aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/Wallabag
diff options
context:
space:
mode:
authorlizyn <zhiylin@outlook.com>2020-02-24 10:04:13 +0800
committerGitHub <noreply@github.com>2020-02-24 10:04:13 +0800
commitb19df31d78d881a43bcf6b3215e5fc3781e8e8aa (patch)
treed0d9861694a4b5e5fbfdbeb53c255ecd15fe9328 /src/Wallabag
parent4d0c632c70ea50d459c3c55ddda2e0f394dd51cb (diff)
parent04d918cae0227c06a41d27fb6533dddbf30dfe71 (diff)
downloadwallabag-b19df31d78d881a43bcf6b3215e5fc3781e8e8aa.tar.gz
wallabag-b19df31d78d881a43bcf6b3215e5fc3781e8e8aa.tar.zst
wallabag-b19df31d78d881a43bcf6b3215e5fc3781e8e8aa.zip
Merge pull request #1 from wallabag/master
Keep up with the master again
Diffstat (limited to 'src/Wallabag')
-rw-r--r--src/Wallabag/AnnotationBundle/Controller/WallabagAnnotationController.php10
-rw-r--r--src/Wallabag/AnnotationBundle/DataFixtures/AnnotationFixtures.php (renamed from src/Wallabag/AnnotationBundle/DataFixtures/ORM/LoadAnnotationData.php)17
-rw-r--r--src/Wallabag/AnnotationBundle/Form/NewAnnotationType.php3
-rw-r--r--src/Wallabag/ApiBundle/Controller/AnnotationRestController.php14
-rw-r--r--src/Wallabag/ApiBundle/Controller/DeveloperController.php6
-rw-r--r--src/Wallabag/ApiBundle/Controller/EntryRestController.php160
-rw-r--r--src/Wallabag/ApiBundle/Controller/SearchRestController.php65
-rw-r--r--src/Wallabag/ApiBundle/Controller/TagRestController.php22
-rw-r--r--src/Wallabag/ApiBundle/Controller/TaggingRuleRestController.php39
-rw-r--r--src/Wallabag/ApiBundle/Controller/UserRestController.php3
-rw-r--r--src/Wallabag/ApiBundle/Controller/WallabagRestController.php43
-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/Client.php2
-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/Repository/ClientRepository.php19
-rw-r--r--src/Wallabag/ApiBundle/Resources/config/routing_rest.yml10
-rw-r--r--src/Wallabag/CoreBundle/Command/CleanDuplicatesCommand.php3
-rw-r--r--src/Wallabag/CoreBundle/Command/GenerateUrlHashesCommand.php96
-rw-r--r--src/Wallabag/CoreBundle/Command/InstallCommand.php19
-rw-r--r--src/Wallabag/CoreBundle/Command/ShowUserCommand.php6
-rw-r--r--src/Wallabag/CoreBundle/Controller/ConfigController.php278
-rw-r--r--src/Wallabag/CoreBundle/Controller/EntryController.php177
-rw-r--r--src/Wallabag/CoreBundle/Controller/ExportController.php8
-rw-r--r--src/Wallabag/CoreBundle/Controller/FeedController.php (renamed from src/Wallabag/CoreBundle/Controller/RssController.php)96
-rw-r--r--src/Wallabag/CoreBundle/Controller/SiteCredentialController.php23
-rw-r--r--src/Wallabag/CoreBundle/Controller/StaticController.php2
-rw-r--r--src/Wallabag/CoreBundle/Controller/TagController.php57
-rw-r--r--src/Wallabag/CoreBundle/DataFixtures/ConfigFixtures.php (renamed from src/Wallabag/CoreBundle/DataFixtures/ORM/LoadConfigData.php)21
-rw-r--r--src/Wallabag/CoreBundle/DataFixtures/EntryFixtures.php138
-rw-r--r--src/Wallabag/CoreBundle/DataFixtures/InternalSettingFixtures.php (renamed from src/Wallabag/CoreBundle/DataFixtures/ORM/LoadSettingData.php)19
-rw-r--r--src/Wallabag/CoreBundle/DataFixtures/ORM/LoadEntryData.php119
-rw-r--r--src/Wallabag/CoreBundle/DataFixtures/ORM/LoadSiteCredentialData.php34
-rw-r--r--src/Wallabag/CoreBundle/DataFixtures/ORM/LoadTagData.php55
-rw-r--r--src/Wallabag/CoreBundle/DataFixtures/SiteCredentialFixtures.php56
-rw-r--r--src/Wallabag/CoreBundle/DataFixtures/TagFixtures.php35
-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/Configuration.php2
-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.php48
-rw-r--r--src/Wallabag/CoreBundle/Entity/Entry.php180
-rw-r--r--src/Wallabag/CoreBundle/Entity/InternalSetting.php36
-rw-r--r--src/Wallabag/CoreBundle/Entity/SiteCredential.php17
-rw-r--r--src/Wallabag/CoreBundle/Entity/Tag.php14
-rw-r--r--src/Wallabag/CoreBundle/Entity/TaggingRule.php14
-rw-r--r--src/Wallabag/CoreBundle/Event/Listener/UserLocaleListener.php11
-rw-r--r--src/Wallabag/CoreBundle/Event/Subscriber/CustomDoctrineORMSubscriber.php3
-rw-r--r--src/Wallabag/CoreBundle/Event/Subscriber/DownloadImagesSubscriber.php8
-rw-r--r--src/Wallabag/CoreBundle/Event/Subscriber/SQLiteCascadeDeleteSubscriber.php5
-rw-r--r--src/Wallabag/CoreBundle/Form/Type/ConfigType.php11
-rw-r--r--src/Wallabag/CoreBundle/Form/Type/EditEntryType.php2
-rw-r--r--src/Wallabag/CoreBundle/Form/Type/EntryFilterType.php9
-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/TaggingRuleImportType.php29
-rw-r--r--src/Wallabag/CoreBundle/Form/Type/UserInformationType.php9
-rw-r--r--src/Wallabag/CoreBundle/GuzzleSiteAuthenticator/GrabySiteConfigBuilder.php24
-rw-r--r--src/Wallabag/CoreBundle/Helper/ContentProxy.php73
-rw-r--r--src/Wallabag/CoreBundle/Helper/DownloadImages.php93
-rw-r--r--src/Wallabag/CoreBundle/Helper/EntriesExport.php114
-rw-r--r--src/Wallabag/CoreBundle/Helper/FileCookieJar.php91
-rw-r--r--src/Wallabag/CoreBundle/Helper/HttpClientFactory.php51
-rw-r--r--src/Wallabag/CoreBundle/Helper/PreparePagerForEntries.php5
-rw-r--r--src/Wallabag/CoreBundle/Helper/RuleBasedTagger.php5
-rw-r--r--src/Wallabag/CoreBundle/Helper/TagsAssigner.php1
-rw-r--r--src/Wallabag/CoreBundle/Helper/UrlHasher.php22
-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.php138
-rw-r--r--src/Wallabag/CoreBundle/Repository/SiteCredentialRepository.php10
-rw-r--r--src/Wallabag/CoreBundle/Repository/TagRepository.php53
-rw-r--r--src/Wallabag/CoreBundle/Resources/config/services.yml52
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.da.yml85
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.de.yml91
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.en.yml97
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.es.yml207
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml85
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml98
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.it.yml85
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml89
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml87
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.pt.yml373
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml89
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.ru.yml130
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.th.yml87
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml113
-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.yml8
-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.yml4
-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/base.html.twig13
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/baggy/Config/index.html.twig182
-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.twig29
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/baggy/Entry/entry.html.twig12
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/baggy/Tag/tags.html.twig24
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/baggy/layout.html.twig1
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/common/Developer/client_parameters.html.twig28
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/common/Developer/howto_app.html.twig4
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/common/Developer/index.html.twig15
-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/Entry/share.html.twig3
-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.twig261
-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/Card/_content.html.twig5
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/_card_actions.html.twig12
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/_card_list.html.twig2
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/_reading_time.html.twig2
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/entries.html.twig24
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/entry.html.twig18
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/new_form.html.twig4
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/search_form.html.twig4
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/material/Tag/tags.html.twig23
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/material/layout.html.twig14
-rw-r--r--src/Wallabag/CoreBundle/Tools/Utils.php9
-rw-r--r--src/Wallabag/CoreBundle/Twig/WallabagExtension.php31
-rw-r--r--src/Wallabag/ImportBundle/Consumer/AbstractConsumer.php7
-rw-r--r--src/Wallabag/ImportBundle/Controller/BrowserController.php4
-rw-r--r--src/Wallabag/ImportBundle/Controller/ChromeController.php2
-rw-r--r--src/Wallabag/ImportBundle/Controller/ElcuratorController.php41
-rw-r--r--src/Wallabag/ImportBundle/Controller/FirefoxController.php2
-rw-r--r--src/Wallabag/ImportBundle/Controller/ImportController.php4
-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/WallabagController.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/AbstractImport.php25
-rw-r--r--src/Wallabag/ImportBundle/Import/BrowserImport.php18
-rw-r--r--src/Wallabag/ImportBundle/Import/ChromeImport.php14
-rw-r--r--src/Wallabag/ImportBundle/Import/ElcuratorImport.php54
-rw-r--r--src/Wallabag/ImportBundle/Import/FirefoxImport.php14
-rw-r--r--src/Wallabag/ImportBundle/Import/ImportChain.php3
-rw-r--r--src/Wallabag/ImportBundle/Import/InstapaperImport.php21
-rw-r--r--src/Wallabag/ImportBundle/Import/PinboardImport.php14
-rw-r--r--src/Wallabag/ImportBundle/Import/PocketImport.php108
-rw-r--r--src/Wallabag/ImportBundle/Import/ReadabilityImport.php14
-rw-r--r--src/Wallabag/ImportBundle/Import/WallabagImport.php16
-rw-r--r--src/Wallabag/ImportBundle/Import/WallabagV1Import.php2
-rw-r--r--src/Wallabag/ImportBundle/Import/WallabagV2Import.php4
-rw-r--r--src/Wallabag/ImportBundle/Resources/config/rabbit.yml8
-rw-r--r--src/Wallabag/ImportBundle/Resources/config/redis.yml21
-rw-r--r--src/Wallabag/ImportBundle/Resources/config/services.yml28
-rw-r--r--src/Wallabag/ImportBundle/Resources/views/Elcurator/index.html.twig3
-rw-r--r--src/Wallabag/UserBundle/Controller/ManageController.php48
-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.php152
-rw-r--r--src/Wallabag/UserBundle/EventListener/CreateConfigListener.php13
-rw-r--r--src/Wallabag/UserBundle/Form/UserType.php16
-rw-r--r--src/Wallabag/UserBundle/Mailer/AuthCodeMailer.php21
-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.twig20
-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/TwoFactor/email_auth_code.html.twig2
-rw-r--r--src/Wallabag/UserBundle/Resources/views/layout.html.twig5
174 files changed, 4481 insertions, 1943 deletions
diff --git a/src/Wallabag/AnnotationBundle/Controller/WallabagAnnotationController.php b/src/Wallabag/AnnotationBundle/Controller/WallabagAnnotationController.php
index 3a7421c7..883ce4a8 100644
--- a/src/Wallabag/AnnotationBundle/Controller/WallabagAnnotationController.php
+++ b/src/Wallabag/AnnotationBundle/Controller/WallabagAnnotationController.php
@@ -16,8 +16,6 @@ class WallabagAnnotationController extends FOSRestController
16 /** 16 /**
17 * Retrieve annotations for an entry. 17 * Retrieve annotations for an entry.
18 * 18 *
19 * @param Entry $entry
20 *
21 * @see Wallabag\ApiBundle\Controller\WallabagRestController 19 * @see Wallabag\ApiBundle\Controller\WallabagRestController
22 * 20 *
23 * @return JsonResponse 21 * @return JsonResponse
@@ -39,9 +37,6 @@ class WallabagAnnotationController extends FOSRestController
39 /** 37 /**
40 * Creates a new annotation. 38 * Creates a new annotation.
41 * 39 *
42 * @param Request $request
43 * @param Entry $entry
44 *
45 * @return JsonResponse 40 * @return JsonResponse
46 * 41 *
47 * @see Wallabag\ApiBundle\Controller\WallabagRestController 42 * @see Wallabag\ApiBundle\Controller\WallabagRestController
@@ -79,9 +74,6 @@ class WallabagAnnotationController extends FOSRestController
79 * 74 *
80 * @ParamConverter("annotation", class="WallabagAnnotationBundle:Annotation") 75 * @ParamConverter("annotation", class="WallabagAnnotationBundle:Annotation")
81 * 76 *
82 * @param Annotation $annotation
83 * @param Request $request
84 *
85 * @return JsonResponse 77 * @return JsonResponse
86 */ 78 */
87 public function putAnnotationAction(Annotation $annotation, Request $request) 79 public function putAnnotationAction(Annotation $annotation, Request $request)
@@ -114,8 +106,6 @@ class WallabagAnnotationController extends FOSRestController
114 * 106 *
115 * @ParamConverter("annotation", class="WallabagAnnotationBundle:Annotation") 107 * @ParamConverter("annotation", class="WallabagAnnotationBundle:Annotation")
116 * 108 *
117 * @param Annotation $annotation
118 *
119 * @return JsonResponse 109 * @return JsonResponse
120 */ 110 */
121 public function deleteAnnotationAction(Annotation $annotation) 111 public function deleteAnnotationAction(Annotation $annotation)
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/AnnotationBundle/Form/NewAnnotationType.php b/src/Wallabag/AnnotationBundle/Form/NewAnnotationType.php
index c73c3ded..48bc2c59 100644
--- a/src/Wallabag/AnnotationBundle/Form/NewAnnotationType.php
+++ b/src/Wallabag/AnnotationBundle/Form/NewAnnotationType.php
@@ -17,7 +17,8 @@ class NewAnnotationType extends AbstractType
17 'empty_data' => '', 17 'empty_data' => '',
18 ]) 18 ])
19 ->add('quote', null, [ 19 ->add('quote', null, [
20 'empty_data' => null, 20 'empty_data' => '',
21 'trim' => false,
21 ]) 22 ])
22 ->add('ranges', CollectionType::class, [ 23 ->add('ranges', CollectionType::class, [
23 'entry_type' => RangeType::class, 24 'entry_type' => RangeType::class,
diff --git a/src/Wallabag/ApiBundle/Controller/AnnotationRestController.php b/src/Wallabag/ApiBundle/Controller/AnnotationRestController.php
index 28d55ba9..66693189 100644
--- a/src/Wallabag/ApiBundle/Controller/AnnotationRestController.php
+++ b/src/Wallabag/ApiBundle/Controller/AnnotationRestController.php
@@ -20,8 +20,6 @@ class AnnotationRestController extends WallabagRestController
20 * } 20 * }
21 * ) 21 * )
22 * 22 *
23 * @param Entry $entry
24 *
25 * @return JsonResponse 23 * @return JsonResponse
26 */ 24 */
27 public function getAnnotationsAction(Entry $entry) 25 public function getAnnotationsAction(Entry $entry)
@@ -39,14 +37,11 @@ class AnnotationRestController extends WallabagRestController
39 * @ApiDoc( 37 * @ApiDoc(
40 * requirements={ 38 * requirements={
41 * {"name"="ranges", "dataType"="array", "requirement"="\w+", "description"="The range array for the annotation"}, 39 * {"name"="ranges", "dataType"="array", "requirement"="\w+", "description"="The range array for the annotation"},
42 * {"name"="quote", "dataType"="string", "required"=false, "description"="Optional, quote for the annotation"}, 40 * {"name"="quote", "dataType"="string", "description"="The annotated text"},
43 * {"name"="text", "dataType"="string", "required"=true, "description"=""}, 41 * {"name"="text", "dataType"="string", "required"=true, "description"="Content of annotation"},
44 * } 42 * }
45 * ) 43 * )
46 * 44 *
47 * @param Request $request
48 * @param Entry $entry
49 *
50 * @return JsonResponse 45 * @return JsonResponse
51 */ 46 */
52 public function postAnnotationAction(Request $request, Entry $entry) 47 public function postAnnotationAction(Request $request, Entry $entry)
@@ -70,9 +65,6 @@ class AnnotationRestController extends WallabagRestController
70 * 65 *
71 * @ParamConverter("annotation", class="WallabagAnnotationBundle:Annotation") 66 * @ParamConverter("annotation", class="WallabagAnnotationBundle:Annotation")
72 * 67 *
73 * @param Annotation $annotation
74 * @param Request $request
75 *
76 * @return JsonResponse 68 * @return JsonResponse
77 */ 69 */
78 public function putAnnotationAction(Annotation $annotation, Request $request) 70 public function putAnnotationAction(Annotation $annotation, Request $request)
@@ -96,8 +88,6 @@ class AnnotationRestController extends WallabagRestController
96 * 88 *
97 * @ParamConverter("annotation", class="WallabagAnnotationBundle:Annotation") 89 * @ParamConverter("annotation", class="WallabagAnnotationBundle:Annotation")
98 * 90 *
99 * @param Annotation $annotation
100 *
101 * @return JsonResponse 91 * @return JsonResponse
102 */ 92 */
103 public function deleteAnnotationAction(Annotation $annotation) 93 public function deleteAnnotationAction(Annotation $annotation)
diff --git a/src/Wallabag/ApiBundle/Controller/DeveloperController.php b/src/Wallabag/ApiBundle/Controller/DeveloperController.php
index c7178017..3224d789 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
@@ -29,8 +29,6 @@ class DeveloperController extends Controller
29 /** 29 /**
30 * Create a client (an app). 30 * Create a client (an app).
31 * 31 *
32 * @param Request $request
33 *
34 * @Route("/developer/client/create", name="developer_create_client") 32 * @Route("/developer/client/create", name="developer_create_client")
35 * 33 *
36 * @return \Symfony\Component\HttpFoundation\Response 34 * @return \Symfony\Component\HttpFoundation\Response
@@ -67,8 +65,6 @@ class DeveloperController extends Controller
67 /** 65 /**
68 * Remove a client. 66 * Remove a client.
69 * 67 *
70 * @param Client $client
71 *
72 * @Route("/developer/client/delete/{id}", requirements={"id" = "\d+"}, name="developer_delete_client") 68 * @Route("/developer/client/delete/{id}", requirements={"id" = "\d+"}, name="developer_delete_client")
73 * 69 *
74 * @return \Symfony\Component\HttpFoundation\RedirectResponse 70 * @return \Symfony\Component\HttpFoundation\RedirectResponse
diff --git a/src/Wallabag/ApiBundle/Controller/EntryRestController.php b/src/Wallabag/ApiBundle/Controller/EntryRestController.php
index 0b4e74a0..c09fdaeb 100644
--- a/src/Wallabag/ApiBundle/Controller/EntryRestController.php
+++ b/src/Wallabag/ApiBundle/Controller/EntryRestController.php
@@ -4,17 +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;
11use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
12use Symfony\Component\HttpKernel\Exception\HttpException; 12use Symfony\Component\HttpKernel\Exception\HttpException;
13use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
14use Wallabag\CoreBundle\Entity\Entry; 13use Wallabag\CoreBundle\Entity\Entry;
15use Wallabag\CoreBundle\Entity\Tag; 14use Wallabag\CoreBundle\Entity\Tag;
16use Wallabag\CoreBundle\Event\EntryDeletedEvent; 15use Wallabag\CoreBundle\Event\EntryDeletedEvent;
17use Wallabag\CoreBundle\Event\EntrySavedEvent; 16use Wallabag\CoreBundle\Event\EntrySavedEvent;
17use Wallabag\CoreBundle\Helper\UrlHasher;
18 18
19class EntryRestController extends WallabagRestController 19class EntryRestController extends WallabagRestController
20{ 20{
@@ -28,8 +28,10 @@ class EntryRestController extends WallabagRestController
28 * @ApiDoc( 28 * @ApiDoc(
29 * parameters={ 29 * parameters={
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"}, 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"},
31 * {"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"},
32 * {"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"}
33 * } 35 * }
34 * ) 36 * )
35 * 37 *
@@ -38,38 +40,49 @@ class EntryRestController extends WallabagRestController
38 public function getEntriesExistsAction(Request $request) 40 public function getEntriesExistsAction(Request $request)
39 { 41 {
40 $this->validateAuthentication(); 42 $this->validateAuthentication();
43 $repo = $this->getDoctrine()->getRepository('WallabagCoreBundle:Entry');
41 44
42 $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');
43 $urls = $request->query->get('urls', []);
44
45 // handle multiple urls first
46 if (!empty($urls)) {
47 $results = [];
48 foreach ($urls as $url) {
49 $res = $this->getDoctrine()
50 ->getRepository('WallabagCoreBundle:Entry')
51 ->findByUrlAndUserId($url, $this->getUser()->getId());
52 46
53 $results[$url] = $this->returnExistInformation($res, $returnId); 47 $hashedUrls = $request->query->get('hashed_urls', []);
54 } 48 $hashedUrl = $request->query->get('hashed_url', '');
55 49 if (!empty($hashedUrl)) {
56 return $this->sendResponse($results); 50 $hashedUrls[] = $hashedUrl;
57 } 51 }
58 52
59 // let's see if it is a simple url? 53 $urls = $request->query->get('urls', []);
60 $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 }
61 65
62 if (empty($url)) { 66 if (empty($hashedUrls)) {
63 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());
64 } 68 }
65 69
66 $res = $this->getDoctrine() 70 $results = [];
67 ->getRepository('WallabagCoreBundle:Entry') 71 foreach ($hashedUrls as $hashedUrlToSearch) {
68 ->findByUrlAndUserId($url, $this->getUser()->getId()); 72 $res = $repo->findByHashedUrlAndUserId($hashedUrlToSearch, $this->getUser()->getId());
73
74 $results[$hashedUrlToSearch] = $this->returnExistInformation($res, $returnId);
75 }
76
77 $results = $this->replaceUrlHashes($results, $urlHashMap);
69 78
70 $exists = $this->returnExistInformation($res, $returnId); 79 if (!empty($url) || !empty($hashedUrl)) {
80 $hu = array_keys($results)[0];
71 81
72 return $this->sendResponse(['exists' => $exists]); 82 return $this->sendResponse(['exists' => $results[$hu]]);
83 }
84
85 return $this->sendResponse($results);
73 } 86 }
74 87
75 /** 88 /**
@@ -79,13 +92,14 @@ class EntryRestController extends WallabagRestController
79 * parameters={ 92 * parameters={
80 * {"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."},
81 * {"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."},
82 * {"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."},
83 * {"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."},
84 * {"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."},
85 * {"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."},
86 * {"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."},
87 * {"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."},
88 * {"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."},
89 * } 103 * }
90 * ) 104 * )
91 * 105 *
@@ -98,24 +112,30 @@ class EntryRestController extends WallabagRestController
98 $isArchived = (null === $request->query->get('archive')) ? null : (bool) $request->query->get('archive'); 112 $isArchived = (null === $request->query->get('archive')) ? null : (bool) $request->query->get('archive');
99 $isStarred = (null === $request->query->get('starred')) ? null : (bool) $request->query->get('starred'); 113 $isStarred = (null === $request->query->get('starred')) ? null : (bool) $request->query->get('starred');
100 $isPublic = (null === $request->query->get('public')) ? null : (bool) $request->query->get('public'); 114 $isPublic = (null === $request->query->get('public')) ? null : (bool) $request->query->get('public');
101 $sort = $request->query->get('sort', 'created'); 115 $sort = strtolower($request->query->get('sort', 'created'));
102 $order = $request->query->get('order', 'desc'); 116 $order = strtolower($request->query->get('order', 'desc'));
103 $page = (int) $request->query->get('page', 1); 117 $page = (int) $request->query->get('page', 1);
104 $perPage = (int) $request->query->get('perPage', 30); 118 $perPage = (int) $request->query->get('perPage', 30);
105 $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', '');
106 $since = $request->query->get('since', 0); 120 $since = $request->query->get('since', 0);
121 $detail = strtolower($request->query->get('detail', 'full'));
107 122
108 /** @var \Pagerfanta\Pagerfanta $pager */ 123 try {
109 $pager = $this->get('wallabag_core.entry_repository')->findEntries( 124 /** @var \Pagerfanta\Pagerfanta $pager */
110 $this->getUser()->getId(), 125 $pager = $this->get('wallabag_core.entry_repository')->findEntries(
111 $isArchived, 126 $this->getUser()->getId(),
112 $isStarred, 127 $isArchived,
113 $isPublic, 128 $isStarred,
114 $sort, 129 $isPublic,
115 $order, 130 $sort,
116 $since, 131 $order,
117 $tags 132 $since,
118 ); 133 $tags,
134 $detail
135 );
136 } catch (\Exception $e) {
137 throw new BadRequestHttpException($e->getMessage());
138 }
119 139
120 $pager->setMaxPerPage($perPage); 140 $pager->setMaxPerPage($perPage);
121 $pager->setCurrentPage($page); 141 $pager->setCurrentPage($page);
@@ -135,8 +155,9 @@ class EntryRestController extends WallabagRestController
135 'perPage' => $perPage, 155 'perPage' => $perPage,
136 'tags' => $tags, 156 'tags' => $tags,
137 'since' => $since, 157 'since' => $since,
158 'detail' => $detail,
138 ], 159 ],
139 UrlGeneratorInterface::ABSOLUTE_URL 160 true
140 ) 161 )
141 ); 162 );
142 163
@@ -344,9 +365,7 @@ class EntryRestController extends WallabagRestController
344 'language' => !empty($data['language']) ? $data['language'] : $entry->getLanguage(), 365 'language' => !empty($data['language']) ? $data['language'] : $entry->getLanguage(),
345 'date' => !empty($data['publishedAt']) ? $data['publishedAt'] : $entry->getPublishedAt(), 366 'date' => !empty($data['publishedAt']) ? $data['publishedAt'] : $entry->getPublishedAt(),
346 // faking the open graph preview picture 367 // faking the open graph preview picture
347 'open_graph' => [ 368 'image' => !empty($data['picture']) ? $data['picture'] : $entry->getPreviewPicture(),
348 'og_image' => !empty($data['picture']) ? $data['picture'] : $entry->getPreviewPicture(),
349 ],
350 'authors' => \is_string($data['authors']) ? explode(',', $data['authors']) : $entry->getPublishedBy(), 369 'authors' => \is_string($data['authors']) ? explode(',', $data['authors']) : $entry->getPublishedBy(),
351 ] 370 ]
352 ); 371 );
@@ -358,7 +377,7 @@ class EntryRestController extends WallabagRestController
358 } 377 }
359 378
360 if (null !== $data['isArchived']) { 379 if (null !== $data['isArchived']) {
361 $entry->setArchived((bool) $data['isArchived']); 380 $entry->updateArchived((bool) $data['isArchived']);
362 } 381 }
363 382
364 if (null !== $data['isStarred']) { 383 if (null !== $data['isStarred']) {
@@ -474,7 +493,7 @@ class EntryRestController extends WallabagRestController
474 } 493 }
475 494
476 if (null !== $data['isArchived']) { 495 if (null !== $data['isArchived']) {
477 $entry->setArchived((bool) $data['isArchived']); 496 $entry->updateArchived((bool) $data['isArchived']);
478 } 497 }
479 498
480 if (null !== $data['isStarred']) { 499 if (null !== $data['isStarred']) {
@@ -545,7 +564,7 @@ class EntryRestController extends WallabagRestController
545 } 564 }
546 565
547 // if refreshing entry failed, don't save it 566 // if refreshing entry failed, don't save it
548 if ($this->getParameter('wallabag_core.fetching_error_message') === $entry->getContent()) { 567 if ($this->container->getParameter('wallabag_core.fetching_error_message') === $entry->getContent()) {
549 return new JsonResponse([], 304); 568 return new JsonResponse([], 304);
550 } 569 }
551 570
@@ -565,18 +584,31 @@ class EntryRestController extends WallabagRestController
565 * @ApiDoc( 584 * @ApiDoc(
566 * requirements={ 585 * requirements={
567 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"} 586 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
587 * },
588 * parameters={
589 * {"name"="expect", "dataType"="string", "required"=false, "format"="id or entry", "description"="Only returns the id instead of the deleted entry's full entity if 'id' is specified. Default to entry"},
568 * } 590 * }
569 * ) 591 * )
570 * 592 *
571 * @return JsonResponse 593 * @return JsonResponse
572 */ 594 */
573 public function deleteEntriesAction(Entry $entry) 595 public function deleteEntriesAction(Entry $entry, Request $request)
574 { 596 {
597 $expect = $request->query->get('expect', 'entry');
598 if (!\in_array($expect, ['id', 'entry'], true)) {
599 throw new BadRequestHttpException(sprintf("expect: 'id' or 'entry' expected, %s given", $expect));
600 }
575 $this->validateAuthentication(); 601 $this->validateAuthentication();
576 $this->validateUserAccess($entry->getUser()->getId()); 602 $this->validateUserAccess($entry->getUser()->getId());
577 603
578 // We copy $entry to keep id in returned object 604 $response = $this->sendResponse([
579 $e = $entry; 605 'id' => $entry->getId(),
606 ]);
607 // We clone $entry to keep id in returned object
608 if ('entry' === $expect) {
609 $e = clone $entry;
610 $response = $this->sendResponse($e);
611 }
580 612
581 $em = $this->getDoctrine()->getManager(); 613 $em = $this->getDoctrine()->getManager();
582 $em->remove($entry); 614 $em->remove($entry);
@@ -585,7 +617,7 @@ class EntryRestController extends WallabagRestController
585 // entry deleted, dispatch event about it! 617 // entry deleted, dispatch event about it!
586 $this->get('event_dispatcher')->dispatch(EntryDeletedEvent::NAME, new EntryDeletedEvent($entry)); 618 $this->get('event_dispatcher')->dispatch(EntryDeletedEvent::NAME, new EntryDeletedEvent($entry));
587 619
588 return $this->sendResponse($e); 620 return $response;
589 } 621 }
590 622
591 /** 623 /**
@@ -769,29 +801,27 @@ class EntryRestController extends WallabagRestController
769 } 801 }
770 802
771 /** 803 /**
772 * Shortcut to send data serialized in json. 804 * Replace the hashedUrl keys in $results with the unhashed URL from the
773 * 805 * request, as recorded in $urlHashMap.
774 * @param mixed $data
775 *
776 * @return JsonResponse
777 */ 806 */
778 private function sendResponse($data) 807 private function replaceUrlHashes(array $results, array $urlHashMap)
779 { 808 {
780 // https://github.com/schmittjoh/JMSSerializerBundle/issues/293 809 $newResults = [];
781 $context = new SerializationContext(); 810 foreach ($results as $hash => $res) {
782 $context->setSerializeNull(true); 811 if (isset($urlHashMap[$hash])) {
783 812 $newResults[$urlHashMap[$hash]] = $res;
784 $json = $this->get('jms_serializer')->serialize($data, 'json', $context); 813 } else {
814 $newResults[$hash] = $res;
815 }
816 }
785 817
786 return (new JsonResponse())->setJson($json); 818 return $newResults;
787 } 819 }
788 820
789 /** 821 /**
790 * Retrieve value from the request. 822 * Retrieve value from the request.
791 * Used for POST & PATCH on a an entry. 823 * Used for POST & PATCH on a an entry.
792 * 824 *
793 * @param Request $request
794 *
795 * @return array 825 * @return array
796 */ 826 */
797 private function retrieveValueFromRequest(Request $request) 827 private function retrieveValueFromRequest(Request $request)
@@ -814,8 +844,8 @@ class EntryRestController extends WallabagRestController
814 /** 844 /**
815 * Return information about the entry if it exist and depending on the id or not. 845 * Return information about the entry if it exist and depending on the id or not.
816 * 846 *
817 * @param Entry|null $entry 847 * @param Entry|bool|null $entry
818 * @param bool $returnId 848 * @param bool $returnId
819 * 849 *
820 * @return bool|int 850 * @return bool|int
821 */ 851 */
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/TagRestController.php b/src/Wallabag/ApiBundle/Controller/TagRestController.php
index c6d6df6a..f3498f55 100644
--- a/src/Wallabag/ApiBundle/Controller/TagRestController.php
+++ b/src/Wallabag/ApiBundle/Controller/TagRestController.php
@@ -46,12 +46,14 @@ class TagRestController extends WallabagRestController
46 $this->validateAuthentication(); 46 $this->validateAuthentication();
47 $label = $request->get('tag', ''); 47 $label = $request->get('tag', '');
48 48
49 $tag = $this->getDoctrine()->getRepository('WallabagCoreBundle:Tag')->findOneByLabel($label); 49 $tags = $this->getDoctrine()->getRepository('WallabagCoreBundle:Tag')->findByLabelsAndUser([$label], $this->getUser()->getId());
50 50
51 if (empty($tag)) { 51 if (empty($tags)) {
52 throw $this->createNotFoundException('Tag not found'); 52 throw $this->createNotFoundException('Tag not found');
53 } 53 }
54 54
55 $tag = $tags[0];
56
55 $this->getDoctrine() 57 $this->getDoctrine()
56 ->getRepository('WallabagCoreBundle:Entry') 58 ->getRepository('WallabagCoreBundle:Entry')
57 ->removeTag($this->getUser()->getId(), $tag); 59 ->removeTag($this->getUser()->getId(), $tag);
@@ -80,15 +82,7 @@ class TagRestController extends WallabagRestController
80 82
81 $tagsLabels = $request->get('tags', ''); 83 $tagsLabels = $request->get('tags', '');
82 84
83 $tags = []; 85 $tags = $this->getDoctrine()->getRepository('WallabagCoreBundle:Tag')->findByLabelsAndUser(explode(',', $tagsLabels), $this->getUser()->getId());
84
85 foreach (explode(',', $tagsLabels) as $tagLabel) {
86 $tagEntity = $this->getDoctrine()->getRepository('WallabagCoreBundle:Tag')->findOneByLabel($tagLabel);
87
88 if (!empty($tagEntity)) {
89 $tags[] = $tagEntity;
90 }
91 }
92 86
93 if (empty($tags)) { 87 if (empty($tags)) {
94 throw $this->createNotFoundException('Tags not found'); 88 throw $this->createNotFoundException('Tags not found');
@@ -120,6 +114,12 @@ class TagRestController extends WallabagRestController
120 { 114 {
121 $this->validateAuthentication(); 115 $this->validateAuthentication();
122 116
117 $tagFromDb = $this->getDoctrine()->getRepository('WallabagCoreBundle:Tag')->findByLabelsAndUser([$tag->getLabel()], $this->getUser()->getId());
118
119 if (empty($tagFromDb)) {
120 throw $this->createNotFoundException('Tag not found');
121 }
122
123 $this->getDoctrine() 123 $this->getDoctrine()
124 ->getRepository('WallabagCoreBundle:Entry') 124 ->getRepository('WallabagCoreBundle:Entry')
125 ->removeTag($this->getUser()->getId(), $tag); 125 ->removeTag($this->getUser()->getId(), $tag);
diff --git a/src/Wallabag/ApiBundle/Controller/TaggingRuleRestController.php b/src/Wallabag/ApiBundle/Controller/TaggingRuleRestController.php
new file mode 100644
index 00000000..2496298a
--- /dev/null
+++ b/src/Wallabag/ApiBundle/Controller/TaggingRuleRestController.php
@@ -0,0 +1,39 @@
1<?php
2
3namespace Wallabag\ApiBundle\Controller;
4
5use JMS\Serializer\SerializationContext;
6use JMS\Serializer\SerializerBuilder;
7use Nelmio\ApiDocBundle\Annotation\ApiDoc;
8use Symfony\Component\HttpFoundation\Response;
9
10class TaggingRuleRestController extends WallabagRestController
11{
12 /**
13 * Export all tagging rules as a json file.
14 *
15 * @ApiDoc()
16 *
17 * @return Response
18 */
19 public function getTaggingruleExportAction()
20 {
21 $this->validateAuthentication();
22
23 $data = SerializerBuilder::create()->build()->serialize(
24 $this->getUser()->getConfig()->getTaggingRules(),
25 'json',
26 SerializationContext::create()->setGroups(['export_tagging_rule'])
27 );
28
29 return Response::create(
30 $data,
31 200,
32 [
33 'Content-type' => 'application/json',
34 'Content-Disposition' => 'attachment; filename="tagging_rules_' . $this->getUser()->getUsername() . '.json"',
35 'Content-Transfer-Encoding' => 'UTF-8',
36 ]
37 );
38 }
39}
diff --git a/src/Wallabag/ApiBundle/Controller/UserRestController.php b/src/Wallabag/ApiBundle/Controller/UserRestController.php
index 3a4dafcd..922ab7bb 100644
--- a/src/Wallabag/ApiBundle/Controller/UserRestController.php
+++ b/src/Wallabag/ApiBundle/Controller/UserRestController.php
@@ -45,7 +45,7 @@ class UserRestController extends WallabagRestController
45 */ 45 */
46 public function putUserAction(Request $request) 46 public function putUserAction(Request $request)
47 { 47 {
48 if (!$this->getParameter('fosuser_registration') || !$this->get('craue_config')->get('api_user_registration')) { 48 if (!$this->container->getParameter('fosuser_registration') || !$this->get('craue_config')->get('api_user_registration')) {
49 $json = $this->get('jms_serializer')->serialize(['error' => "Server doesn't allow registrations"], 'json'); 49 $json = $this->get('jms_serializer')->serialize(['error' => "Server doesn't allow registrations"], 'json');
50 50
51 return (new JsonResponse()) 51 return (new JsonResponse())
@@ -119,7 +119,6 @@ class UserRestController extends WallabagRestController
119 /** 119 /**
120 * Send user response. 120 * Send user response.
121 * 121 *
122 * @param User $user
123 * @param string $group Used to define with serialized group might be used 122 * @param string $group Used to define with serialized group might be used
124 * @param int $status HTTP Status code to send 123 * @param int $status HTTP Status code to send
125 * 124 *
diff --git a/src/Wallabag/ApiBundle/Controller/WallabagRestController.php b/src/Wallabag/ApiBundle/Controller/WallabagRestController.php
index 7d8cfbba..44fd9683 100644
--- a/src/Wallabag/ApiBundle/Controller/WallabagRestController.php
+++ b/src/Wallabag/ApiBundle/Controller/WallabagRestController.php
@@ -2,18 +2,21 @@
2 2
3namespace Wallabag\ApiBundle\Controller; 3namespace Wallabag\ApiBundle\Controller;
4 4
5use FOS\RestBundle\Controller\FOSRestController; 5use FOS\RestBundle\Controller\AbstractFOSRestController;
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;
9 10
10class WallabagRestController extends FOSRestController 11class WallabagRestController extends AbstractFOSRestController
11{ 12{
12 /** 13 /**
13 * Retrieve version number. 14 * Retrieve version number.
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/Client.php b/src/Wallabag/ApiBundle/Entity/Client.php
index e6f98f98..78349820 100644
--- a/src/Wallabag/ApiBundle/Entity/Client.php
+++ b/src/Wallabag/ApiBundle/Entity/Client.php
@@ -11,7 +11,7 @@ use Wallabag\UserBundle\Entity\User;
11 11
12/** 12/**
13 * @ORM\Table("oauth2_clients") 13 * @ORM\Table("oauth2_clients")
14 * @ORM\Entity 14 * @ORM\Entity(repositoryClass="Wallabag\ApiBundle\Repository\ClientRepository")
15 */ 15 */
16class Client extends BaseClient 16class Client extends BaseClient
17{ 17{
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/Repository/ClientRepository.php b/src/Wallabag/ApiBundle/Repository/ClientRepository.php
new file mode 100644
index 00000000..fc14262e
--- /dev/null
+++ b/src/Wallabag/ApiBundle/Repository/ClientRepository.php
@@ -0,0 +1,19 @@
1<?php
2
3namespace Wallabag\ApiBundle\Repository;
4
5use Doctrine\ORM\EntityRepository;
6
7class ClientRepository extends EntityRepository
8{
9 public function findOneBy(array $criteria, array $orderBy = null)
10 {
11 if (!empty($criteria['id'])) {
12 // cast client id to be an integer to avoid postgres error:
13 // "invalid input syntax for integer"
14 $criteria['id'] = (int) $criteria['id'];
15 }
16
17 return parent::findOneBy($criteria, $orderBy);
18 }
19}
diff --git a/src/Wallabag/ApiBundle/Resources/config/routing_rest.yml b/src/Wallabag/ApiBundle/Resources/config/routing_rest.yml
index c0283e71..98efeeb1 100644
--- a/src/Wallabag/ApiBundle/Resources/config/routing_rest.yml
+++ b/src/Wallabag/ApiBundle/Resources/config/routing_rest.yml
@@ -3,11 +3,21 @@ 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"
9 name_prefix: api_ 14 name_prefix: api_
10 15
16tagging_rule:
17 type: rest
18 resource: "WallabagApiBundle:TaggingRuleRest"
19 name_prefix: api_
20
11annotation: 21annotation:
12 type: rest 22 type: rest
13 resource: "WallabagApiBundle:AnnotationRest" 23 resource: "WallabagApiBundle:AnnotationRest"
diff --git a/src/Wallabag/CoreBundle/Command/CleanDuplicatesCommand.php b/src/Wallabag/CoreBundle/Command/CleanDuplicatesCommand.php
index 99170967..64b91520 100644
--- a/src/Wallabag/CoreBundle/Command/CleanDuplicatesCommand.php
+++ b/src/Wallabag/CoreBundle/Command/CleanDuplicatesCommand.php
@@ -63,9 +63,6 @@ class CleanDuplicatesCommand extends ContainerAwareCommand
63 return 0; 63 return 0;
64 } 64 }
65 65
66 /**
67 * @param User $user
68 */
69 private function cleanDuplicates(User $user) 66 private function cleanDuplicates(User $user)
70 { 67 {
71 $em = $this->getContainer()->get('doctrine.orm.entity_manager'); 68 $em = $this->getContainer()->get('doctrine.orm.entity_manager');
diff --git a/src/Wallabag/CoreBundle/Command/GenerateUrlHashesCommand.php b/src/Wallabag/CoreBundle/Command/GenerateUrlHashesCommand.php
new file mode 100644
index 00000000..a0e9221e
--- /dev/null
+++ b/src/Wallabag/CoreBundle/Command/GenerateUrlHashesCommand.php
@@ -0,0 +1,96 @@
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 private function generateHashedUrls(User $user)
58 {
59 $em = $this->getContainer()->get('doctrine.orm.entity_manager');
60 $repo = $this->getDoctrine()->getRepository('WallabagCoreBundle:Entry');
61
62 $entries = $repo->findByUser($user->getId());
63
64 $i = 1;
65 foreach ($entries as $entry) {
66 $entry->setHashedUrl(UrlHasher::hashUrl($entry->getUrl()));
67 $em->persist($entry);
68
69 if (0 === ($i % 20)) {
70 $em->flush();
71 }
72 ++$i;
73 }
74
75 $em->flush();
76
77 $this->output->writeln(sprintf('Generated hashed urls for user: %s', $user->getUserName()));
78 }
79
80 /**
81 * Fetches a user from its username.
82 *
83 * @param string $username
84 *
85 * @return User
86 */
87 private function getUser($username)
88 {
89 return $this->getDoctrine()->getRepository('WallabagUserBundle:User')->findOneByUserName($username);
90 }
91
92 private function getDoctrine()
93 {
94 return $this->getContainer()->get('doctrine');
95 }
96}
diff --git a/src/Wallabag/CoreBundle/Command/InstallCommand.php b/src/Wallabag/CoreBundle/Command/InstallCommand.php
index 3c76545c..3aa332f1 100644
--- a/src/Wallabag/CoreBundle/Command/InstallCommand.php
+++ b/src/Wallabag/CoreBundle/Command/InstallCommand.php
@@ -2,7 +2,6 @@
2 2
3namespace Wallabag\CoreBundle\Command; 3namespace Wallabag\CoreBundle\Command;
4 4
5use Craue\ConfigBundle\Entity\Setting;
6use FOS\UserBundle\Event\UserEvent; 5use FOS\UserBundle\Event\UserEvent;
7use FOS\UserBundle\FOSUserEvents; 6use FOS\UserBundle\FOSUserEvents;
8use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand; 7use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
@@ -13,6 +12,7 @@ use Symfony\Component\Console\Output\BufferedOutput;
13use Symfony\Component\Console\Output\OutputInterface; 12use Symfony\Component\Console\Output\OutputInterface;
14use Symfony\Component\Console\Question\Question; 13use Symfony\Component\Console\Question\Question;
15use Symfony\Component\Console\Style\SymfonyStyle; 14use Symfony\Component\Console\Style\SymfonyStyle;
15use Wallabag\CoreBundle\Entity\InternalSetting;
16 16
17class InstallCommand extends ContainerAwareCommand 17class InstallCommand extends ContainerAwareCommand
18{ 18{
@@ -54,7 +54,7 @@ class InstallCommand extends ContainerAwareCommand
54 54
55 $this->io = new SymfonyStyle($input, $output); 55 $this->io = new SymfonyStyle($input, $output);
56 56
57 $this->io->title('Wallabag installer'); 57 $this->io->title('wallabag installer');
58 58
59 $this 59 $this
60 ->checkRequirements() 60 ->checkRequirements()
@@ -63,7 +63,7 @@ class InstallCommand extends ContainerAwareCommand
63 ->setupConfig() 63 ->setupConfig()
64 ; 64 ;
65 65
66 $this->io->success('Wallabag has been successfully installed.'); 66 $this->io->success('wallabag has been successfully installed.');
67 $this->io->success('You can now configure your web server, see https://doc.wallabag.org'); 67 $this->io->success('You can now configure your web server, see https://doc.wallabag.org');
68 } 68 }
69 69
@@ -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');
@@ -275,10 +276,10 @@ class InstallCommand extends ContainerAwareCommand
275 $em = $this->getContainer()->get('doctrine.orm.entity_manager'); 276 $em = $this->getContainer()->get('doctrine.orm.entity_manager');
276 277
277 // cleanup before insert new stuff 278 // cleanup before insert new stuff
278 $em->createQuery('DELETE FROM CraueConfigBundle:Setting')->execute(); 279 $em->createQuery('DELETE FROM WallabagCoreBundle:InternalSetting')->execute();
279 280
280 foreach ($this->getContainer()->getParameter('wallabag_core.default_internal_settings') as $setting) { 281 foreach ($this->getContainer()->getParameter('wallabag_core.default_internal_settings') as $setting) {
281 $newSetting = new Setting(); 282 $newSetting = new InternalSetting();
282 $newSetting->setName($setting['name']); 283 $newSetting->setName($setting['name']);
283 $newSetting->setValue($setting['value']); 284 $newSetting->setValue($setting['value']);
284 $newSetting->setSection($setting['section']); 285 $newSetting->setSection($setting['section']);
@@ -325,9 +326,7 @@ class InstallCommand extends ContainerAwareCommand
325 if (0 !== $exitCode) { 326 if (0 !== $exitCode) {
326 $this->getApplication()->setAutoExit(true); 327 $this->getApplication()->setAutoExit(true);
327 328
328 throw new \RuntimeException( 329 throw new \RuntimeException('The command "' . $command . "\" generates some errors: \n\n" . $output->fetch());
329 'The command "' . $command . "\" generates some errors: \n\n"
330 . $output->fetch());
331 } 330 }
332 331
333 return $this; 332 return $this;
diff --git a/src/Wallabag/CoreBundle/Command/ShowUserCommand.php b/src/Wallabag/CoreBundle/Command/ShowUserCommand.php
index a0184267..87bccf71 100644
--- a/src/Wallabag/CoreBundle/Command/ShowUserCommand.php
+++ b/src/Wallabag/CoreBundle/Command/ShowUserCommand.php
@@ -46,9 +46,6 @@ class ShowUserCommand extends ContainerAwareCommand
46 return 0; 46 return 0;
47 } 47 }
48 48
49 /**
50 * @param User $user
51 */
52 private function showUser(User $user) 49 private function showUser(User $user)
53 { 50 {
54 $this->io->listing([ 51 $this->io->listing([
@@ -57,7 +54,8 @@ class ShowUserCommand extends ContainerAwareCommand
57 sprintf('Display name: %s', $user->getName()), 54 sprintf('Display name: %s', $user->getName()),
58 sprintf('Creation date: %s', $user->getCreatedAt()->format('Y-m-d H:i:s')), 55 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'), 56 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'), 57 sprintf('2FA (email) activated: %s', $user->isEmailTwoFactor() ? 'yes' : 'no'),
58 sprintf('2FA (OTP) activated: %s', $user->isGoogleAuthenticatorEnabled() ? 'yes' : 'no'),
61 ]); 59 ]);
62 } 60 }
63 61
diff --git a/src/Wallabag/CoreBundle/Controller/ConfigController.php b/src/Wallabag/CoreBundle/Controller/ConfigController.php
index b999c539..6655ef93 100644
--- a/src/Wallabag/CoreBundle/Controller/ConfigController.php
+++ b/src/Wallabag/CoreBundle/Controller/ConfigController.php
@@ -2,17 +2,23 @@
2 2
3namespace Wallabag\CoreBundle\Controller; 3namespace Wallabag\CoreBundle\Controller;
4 4
5use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; 5use JMS\Serializer\SerializationContext;
6use JMS\Serializer\SerializerBuilder;
7use PragmaRX\Recovery\Recovery as BackupCodes;
6use Symfony\Bundle\FrameworkBundle\Controller\Controller; 8use Symfony\Bundle\FrameworkBundle\Controller\Controller;
7use Symfony\Component\HttpFoundation\JsonResponse; 9use Symfony\Component\HttpFoundation\JsonResponse;
8use Symfony\Component\HttpFoundation\RedirectResponse; 10use Symfony\Component\HttpFoundation\RedirectResponse;
9use Symfony\Component\HttpFoundation\Request; 11use Symfony\Component\HttpFoundation\Request;
12use Symfony\Component\HttpFoundation\Response;
10use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; 13use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
14use Symfony\Component\Routing\Annotation\Route;
15use Symfony\Component\Validator\Constraints\Locale as LocaleConstraint;
11use Wallabag\CoreBundle\Entity\Config; 16use Wallabag\CoreBundle\Entity\Config;
12use Wallabag\CoreBundle\Entity\TaggingRule; 17use Wallabag\CoreBundle\Entity\TaggingRule;
13use Wallabag\CoreBundle\Form\Type\ChangePasswordType; 18use Wallabag\CoreBundle\Form\Type\ChangePasswordType;
14use Wallabag\CoreBundle\Form\Type\ConfigType; 19use Wallabag\CoreBundle\Form\Type\ConfigType;
15use Wallabag\CoreBundle\Form\Type\RssType; 20use Wallabag\CoreBundle\Form\Type\FeedType;
21use Wallabag\CoreBundle\Form\Type\TaggingRuleImportType;
16use Wallabag\CoreBundle\Form\Type\TaggingRuleType; 22use Wallabag\CoreBundle\Form\Type\TaggingRuleType;
17use Wallabag\CoreBundle\Form\Type\UserInformationType; 23use Wallabag\CoreBundle\Form\Type\UserInformationType;
18use Wallabag\CoreBundle\Tools\Utils; 24use Wallabag\CoreBundle\Tools\Utils;
@@ -20,8 +26,6 @@ use Wallabag\CoreBundle\Tools\Utils;
20class ConfigController extends Controller 26class ConfigController extends Controller
21{ 27{
22 /** 28 /**
23 * @param Request $request
24 *
25 * @Route("/config", name="config") 29 * @Route("/config", name="config")
26 */ 30 */
27 public function indexAction(Request $request) 31 public function indexAction(Request $request)
@@ -45,7 +49,7 @@ class ConfigController extends Controller
45 $activeTheme = $this->get('liip_theme.active_theme'); 49 $activeTheme = $this->get('liip_theme.active_theme');
46 $activeTheme->setName($config->getTheme()); 50 $activeTheme->setName($config->getTheme());
47 51
48 $this->get('session')->getFlashBag()->add( 52 $this->addFlash(
49 'notice', 53 'notice',
50 'flashes.config.notice.config_saved' 54 'flashes.config.notice.config_saved'
51 ); 55 );
@@ -67,7 +71,7 @@ class ConfigController extends Controller
67 $userManager->updateUser($user, true); 71 $userManager->updateUser($user, true);
68 } 72 }
69 73
70 $this->get('session')->getFlashBag()->add('notice', $message); 74 $this->addFlash('notice', $message);
71 75
72 return $this->redirect($this->generateUrl('config') . '#set4'); 76 return $this->redirect($this->generateUrl('config') . '#set4');
73 } 77 }
@@ -82,7 +86,7 @@ class ConfigController extends Controller
82 if ($userForm->isSubmitted() && $userForm->isValid()) { 86 if ($userForm->isSubmitted() && $userForm->isValid()) {
83 $userManager->updateUser($user, true); 87 $userManager->updateUser($user, true);
84 88
85 $this->get('session')->getFlashBag()->add( 89 $this->addFlash(
86 'notice', 90 'notice',
87 'flashes.config.notice.user_updated' 91 'flashes.config.notice.user_updated'
88 ); 92 );
@@ -90,17 +94,17 @@ class ConfigController extends Controller
90 return $this->redirect($this->generateUrl('config') . '#set3'); 94 return $this->redirect($this->generateUrl('config') . '#set3');
91 } 95 }
92 96
93 // handle rss information 97 // handle feed information
94 $rssForm = $this->createForm(RssType::class, $config, ['action' => $this->generateUrl('config') . '#set2']); 98 $feedForm = $this->createForm(FeedType::class, $config, ['action' => $this->generateUrl('config') . '#set2']);
95 $rssForm->handleRequest($request); 99 $feedForm->handleRequest($request);
96 100
97 if ($rssForm->isSubmitted() && $rssForm->isValid()) { 101 if ($feedForm->isSubmitted() && $feedForm->isValid()) {
98 $em->persist($config); 102 $em->persist($config);
99 $em->flush(); 103 $em->flush();
100 104
101 $this->get('session')->getFlashBag()->add( 105 $this->addFlash(
102 'notice', 106 'notice',
103 'flashes.config.notice.rss_updated' 107 'flashes.config.notice.feed_updated'
104 ); 108 );
105 109
106 return $this->redirect($this->generateUrl('config') . '#set2'); 110 return $this->redirect($this->generateUrl('config') . '#set2');
@@ -130,7 +134,7 @@ class ConfigController extends Controller
130 $em->persist($taggingRule); 134 $em->persist($taggingRule);
131 $em->flush(); 135 $em->flush();
132 136
133 $this->get('session')->getFlashBag()->add( 137 $this->addFlash(
134 'notice', 138 'notice',
135 'flashes.config.notice.tagging_rules_updated' 139 'flashes.config.notice.tagging_rules_updated'
136 ); 140 );
@@ -138,28 +142,168 @@ class ConfigController extends Controller
138 return $this->redirect($this->generateUrl('config') . '#set5'); 142 return $this->redirect($this->generateUrl('config') . '#set5');
139 } 143 }
140 144
145 // handle tagging rules import
146 $taggingRulesImportform = $this->createForm(TaggingRuleImportType::class);
147 $taggingRulesImportform->handleRequest($request);
148
149 if ($taggingRulesImportform->isSubmitted() && $taggingRulesImportform->isValid()) {
150 $message = 'flashes.config.notice.tagging_rules_not_imported';
151 $file = $taggingRulesImportform->get('file')->getData();
152
153 if (null !== $file && $file->isValid() && \in_array($file->getClientMimeType(), ['application/json', 'application/octet-stream'], true)) {
154 $content = json_decode(file_get_contents($file->getPathname()), true);
155
156 if (\is_array($content)) {
157 foreach ($content as $rule) {
158 $taggingRule = new TaggingRule();
159 $taggingRule->setRule($rule['rule']);
160 $taggingRule->setTags($rule['tags']);
161 $taggingRule->setConfig($config);
162 $em->persist($taggingRule);
163 }
164
165 $em->flush();
166
167 $message = 'flashes.config.notice.tagging_rules_imported';
168 }
169 }
170
171 $this->addFlash('notice', $message);
172
173 return $this->redirect($this->generateUrl('config') . '#set5');
174 }
175
141 return $this->render('WallabagCoreBundle:Config:index.html.twig', [ 176 return $this->render('WallabagCoreBundle:Config:index.html.twig', [
142 'form' => [ 177 'form' => [
143 'config' => $configForm->createView(), 178 'config' => $configForm->createView(),
144 'rss' => $rssForm->createView(), 179 'feed' => $feedForm->createView(),
145 'pwd' => $pwdForm->createView(), 180 'pwd' => $pwdForm->createView(),
146 'user' => $userForm->createView(), 181 'user' => $userForm->createView(),
147 'new_tagging_rule' => $newTaggingRule->createView(), 182 'new_tagging_rule' => $newTaggingRule->createView(),
183 'import_tagging_rule' => $taggingRulesImportform->createView(),
148 ], 184 ],
149 'rss' => [ 185 'feed' => [
150 'username' => $user->getUsername(), 186 'username' => $user->getUsername(),
151 'token' => $config->getRssToken(), 187 'token' => $config->getFeedToken(),
152 ], 188 ],
153 'twofactor_auth' => $this->getParameter('twofactor_auth'), 189 'twofactor_auth' => $this->getParameter('twofactor_auth'),
154 'wallabag_url' => $this->getParameter('domain_name'), 190 'wallabag_url' => $this->getParameter('domain_name'),
155 'enabled_users' => $this->get('wallabag_user.user_repository') 191 'enabled_users' => $this->get('wallabag_user.user_repository')->getSumEnabledUsers(),
156 ->getSumEnabledUsers(), 192 ]);
193 }
194
195 /**
196 * Enable 2FA using email.
197 *
198 * @Route("/config/otp/email", name="config_otp_email")
199 */
200 public function otpEmailAction()
201 {
202 if (!$this->getParameter('twofactor_auth')) {
203 return $this->createNotFoundException('two_factor not enabled');
204 }
205
206 $user = $this->getUser();
207
208 $user->setGoogleAuthenticatorSecret(null);
209 $user->setBackupCodes(null);
210 $user->setEmailTwoFactor(true);
211
212 $this->container->get('fos_user.user_manager')->updateUser($user, true);
213
214 $this->addFlash(
215 'notice',
216 'flashes.config.notice.otp_enabled'
217 );
218
219 return $this->redirect($this->generateUrl('config') . '#set3');
220 }
221
222 /**
223 * Enable 2FA using OTP app, user will need to confirm the generated code from the app.
224 *
225 * @Route("/config/otp/app", name="config_otp_app")
226 */
227 public function otpAppAction()
228 {
229 if (!$this->getParameter('twofactor_auth')) {
230 return $this->createNotFoundException('two_factor not enabled');
231 }
232
233 $user = $this->getUser();
234 $secret = $this->get('scheb_two_factor.security.google_authenticator')->generateSecret();
235
236 $user->setGoogleAuthenticatorSecret($secret);
237 $user->setEmailTwoFactor(false);
238
239 $backupCodes = (new BackupCodes())->toArray();
240 $backupCodesHashed = array_map(
241 function ($backupCode) {
242 return password_hash($backupCode, PASSWORD_DEFAULT);
243 },
244 $backupCodes
245 );
246
247 $user->setBackupCodes($backupCodesHashed);
248
249 $this->container->get('fos_user.user_manager')->updateUser($user, true);
250
251 return $this->render('WallabagCoreBundle:Config:otp_app.html.twig', [
252 'backupCodes' => $backupCodes,
253 'qr_code' => $this->get('scheb_two_factor.security.google_authenticator')->getQRContent($user),
157 ]); 254 ]);
158 } 255 }
159 256
160 /** 257 /**
161 * @param Request $request 258 * Cancelling 2FA using OTP app.
162 * 259 *
260 * @Route("/config/otp/app/cancel", name="config_otp_app_cancel")
261 */
262 public function otpAppCancelAction()
263 {
264 if (!$this->getParameter('twofactor_auth')) {
265 return $this->createNotFoundException('two_factor not enabled');
266 }
267
268 $user = $this->getUser();
269 $user->setGoogleAuthenticatorSecret(null);
270 $user->setBackupCodes(null);
271
272 $this->container->get('fos_user.user_manager')->updateUser($user, true);
273
274 return $this->redirect($this->generateUrl('config') . '#set3');
275 }
276
277 /**
278 * Validate OTP code.
279 *
280 * @Route("/config/otp/app/check", name="config_otp_app_check")
281 */
282 public function otpAppCheckAction(Request $request)
283 {
284 $isValid = $this->get('scheb_two_factor.security.google_authenticator')->checkCode(
285 $this->getUser(),
286 $request->get('_auth_code')
287 );
288
289 if (true === $isValid) {
290 $this->addFlash(
291 'notice',
292 'flashes.config.notice.otp_enabled'
293 );
294
295 return $this->redirect($this->generateUrl('config') . '#set3');
296 }
297
298 $this->addFlash(
299 'two_factor',
300 'scheb_two_factor.code_invalid'
301 );
302
303 return $this->redirect($this->generateUrl('config_otp_app'));
304 }
305
306 /**
163 * @Route("/generate-token", name="generate_token") 307 * @Route("/generate-token", name="generate_token")
164 * 308 *
165 * @return RedirectResponse|JsonResponse 309 * @return RedirectResponse|JsonResponse
@@ -167,28 +311,52 @@ class ConfigController extends Controller
167 public function generateTokenAction(Request $request) 311 public function generateTokenAction(Request $request)
168 { 312 {
169 $config = $this->getConfig(); 313 $config = $this->getConfig();
170 $config->setRssToken(Utils::generateToken()); 314 $config->setFeedToken(Utils::generateToken());
171 315
172 $em = $this->getDoctrine()->getManager(); 316 $em = $this->getDoctrine()->getManager();
173 $em->persist($config); 317 $em->persist($config);
174 $em->flush(); 318 $em->flush();
175 319
176 if ($request->isXmlHttpRequest()) { 320 if ($request->isXmlHttpRequest()) {
177 return new JsonResponse(['token' => $config->getRssToken()]); 321 return new JsonResponse(['token' => $config->getFeedToken()]);
178 } 322 }
179 323
180 $this->get('session')->getFlashBag()->add( 324 $this->addFlash(
181 'notice', 325 'notice',
182 'flashes.config.notice.rss_token_updated' 326 'flashes.config.notice.feed_token_updated'
183 ); 327 );
184 328
185 return $this->redirect($this->generateUrl('config') . '#set2'); 329 return $this->redirect($this->generateUrl('config') . '#set2');
186 } 330 }
187 331
188 /** 332 /**
189 * Deletes a tagging rule and redirect to the config homepage. 333 * @Route("/revoke-token", name="revoke_token")
190 * 334 *
191 * @param TaggingRule $rule 335 * @return RedirectResponse|JsonResponse
336 */
337 public function revokeTokenAction(Request $request)
338 {
339 $config = $this->getConfig();
340 $config->setFeedToken(null);
341
342 $em = $this->getDoctrine()->getManager();
343 $em->persist($config);
344 $em->flush();
345
346 if ($request->isXmlHttpRequest()) {
347 return new JsonResponse();
348 }
349
350 $this->addFlash(
351 'notice',
352 'flashes.config.notice.feed_token_revoked'
353 );
354
355 return $this->redirect($this->generateUrl('config') . '#set2');
356 }
357
358 /**
359 * Deletes a tagging rule and redirect to the config homepage.
192 * 360 *
193 * @Route("/tagging-rule/delete/{id}", requirements={"id" = "\d+"}, name="delete_tagging_rule") 361 * @Route("/tagging-rule/delete/{id}", requirements={"id" = "\d+"}, name="delete_tagging_rule")
194 * 362 *
@@ -202,7 +370,7 @@ class ConfigController extends Controller
202 $em->remove($rule); 370 $em->remove($rule);
203 $em->flush(); 371 $em->flush();
204 372
205 $this->get('session')->getFlashBag()->add( 373 $this->addFlash(
206 'notice', 374 'notice',
207 'flashes.config.notice.tagging_rules_deleted' 375 'flashes.config.notice.tagging_rules_deleted'
208 ); 376 );
@@ -213,8 +381,6 @@ class ConfigController extends Controller
213 /** 381 /**
214 * Edit a tagging rule. 382 * Edit a tagging rule.
215 * 383 *
216 * @param TaggingRule $rule
217 *
218 * @Route("/tagging-rule/edit/{id}", requirements={"id" = "\d+"}, name="edit_tagging_rule") 384 * @Route("/tagging-rule/edit/{id}", requirements={"id" = "\d+"}, name="edit_tagging_rule")
219 * 385 *
220 * @return RedirectResponse 386 * @return RedirectResponse
@@ -268,7 +434,7 @@ class ConfigController extends Controller
268 break; 434 break;
269 } 435 }
270 436
271 $this->get('session')->getFlashBag()->add( 437 $this->addFlash(
272 'notice', 438 'notice',
273 'flashes.config.notice.' . $type . '_reset' 439 'flashes.config.notice.' . $type . '_reset'
274 ); 440 );
@@ -281,8 +447,6 @@ class ConfigController extends Controller
281 * 447 *
282 * @Route("/account/delete", name="delete_account") 448 * @Route("/account/delete", name="delete_account")
283 * 449 *
284 * @param Request $request
285 *
286 * @throws AccessDeniedHttpException 450 * @throws AccessDeniedHttpException
287 * 451 *
288 * @return \Symfony\Component\HttpFoundation\RedirectResponse 452 * @return \Symfony\Component\HttpFoundation\RedirectResponse
@@ -313,8 +477,6 @@ class ConfigController extends Controller
313 * 477 *
314 * @Route("/config/view-mode", name="switch_view_mode") 478 * @Route("/config/view-mode", name="switch_view_mode")
315 * 479 *
316 * @param Request $request
317 *
318 * @return \Symfony\Component\HttpFoundation\RedirectResponse 480 * @return \Symfony\Component\HttpFoundation\RedirectResponse
319 */ 481 */
320 public function changeViewModeAction(Request $request) 482 public function changeViewModeAction(Request $request)
@@ -330,6 +492,52 @@ class ConfigController extends Controller
330 } 492 }
331 493
332 /** 494 /**
495 * Change the locale for the current user.
496 *
497 * @param string $language
498 *
499 * @Route("/locale/{language}", name="changeLocale")
500 *
501 * @return \Symfony\Component\HttpFoundation\RedirectResponse
502 */
503 public function setLocaleAction(Request $request, $language = null)
504 {
505 $errors = $this->get('validator')->validate($language, (new LocaleConstraint()));
506
507 if (0 === \count($errors)) {
508 $request->getSession()->set('_locale', $language);
509 }
510
511 return $this->redirect($request->headers->get('referer', $this->generateUrl('homepage')));
512 }
513
514 /**
515 * Export tagging rules for the logged in user.
516 *
517 * @Route("/tagging-rule/export", name="export_tagging_rule")
518 *
519 * @return Response
520 */
521 public function exportTaggingRulesAction()
522 {
523 $data = SerializerBuilder::create()->build()->serialize(
524 $this->getUser()->getConfig()->getTaggingRules(),
525 'json',
526 SerializationContext::create()->setGroups(['export_tagging_rule'])
527 );
528
529 return Response::create(
530 $data,
531 200,
532 [
533 'Content-type' => 'application/json',
534 'Content-Disposition' => 'attachment; filename="tagging_rules_' . $this->getUser()->getUsername() . '.json"',
535 'Content-Transfer-Encoding' => 'UTF-8',
536 ]
537 );
538 }
539
540 /**
333 * Remove all tags for given tags and a given user and cleanup orphan tags. 541 * Remove all tags for given tags and a given user and cleanup orphan tags.
334 * 542 *
335 * @param array $tags 543 * @param array $tags
@@ -395,8 +603,6 @@ class ConfigController extends Controller
395 603
396 /** 604 /**
397 * Validate that a rule can be edited/deleted by the current user. 605 * Validate that a rule can be edited/deleted by the current user.
398 *
399 * @param TaggingRule $rule
400 */ 606 */
401 private function validateRuleAction(TaggingRule $rule) 607 private function validateRuleAction(TaggingRule $rule)
402 { 608 {
diff --git a/src/Wallabag/CoreBundle/Controller/EntryController.php b/src/Wallabag/CoreBundle/Controller/EntryController.php
index b7fdea27..ba5bfbe2 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;
@@ -20,8 +21,7 @@ use Wallabag\CoreBundle\Form\Type\SearchEntryType;
20class EntryController extends Controller 21class EntryController extends Controller
21{ 22{
22 /** 23 /**
23 * @param Request $request 24 * @param int $page
24 * @param int $page
25 * 25 *
26 * @Route("/search/{page}", name="search", defaults={"page" = 1}) 26 * @Route("/search/{page}", name="search", defaults={"page" = 1})
27 * 27 *
@@ -52,8 +52,6 @@ class EntryController extends Controller
52 } 52 }
53 53
54 /** 54 /**
55 * @param Request $request
56 *
57 * @Route("/new-entry", name="new_entry") 55 * @Route("/new-entry", name="new_entry")
58 * 56 *
59 * @return \Symfony\Component\HttpFoundation\Response 57 * @return \Symfony\Component\HttpFoundation\Response
@@ -96,8 +94,6 @@ class EntryController extends Controller
96 } 94 }
97 95
98 /** 96 /**
99 * @param Request $request
100 *
101 * @Route("/bookmarklet", name="bookmarklet") 97 * @Route("/bookmarklet", name="bookmarklet")
102 * 98 *
103 * @return \Symfony\Component\HttpFoundation\Response 99 * @return \Symfony\Component\HttpFoundation\Response
@@ -134,9 +130,6 @@ class EntryController extends Controller
134 /** 130 /**
135 * Edit an entry content. 131 * Edit an entry content.
136 * 132 *
137 * @param Request $request
138 * @param Entry $entry
139 *
140 * @Route("/edit/{id}", requirements={"id" = "\d+"}, name="edit") 133 * @Route("/edit/{id}", requirements={"id" = "\d+"}, name="edit")
141 * 134 *
142 * @return \Symfony\Component\HttpFoundation\Response 135 * @return \Symfony\Component\HttpFoundation\Response
@@ -170,8 +163,7 @@ class EntryController extends Controller
170 /** 163 /**
171 * Shows all entries for current user. 164 * Shows all entries for current user.
172 * 165 *
173 * @param Request $request 166 * @param int $page
174 * @param int $page
175 * 167 *
176 * @Route("/all/list/{page}", name="all", defaults={"page" = "1"}) 168 * @Route("/all/list/{page}", name="all", defaults={"page" = "1"})
177 * 169 *
@@ -185,8 +177,7 @@ class EntryController extends Controller
185 /** 177 /**
186 * Shows unread entries for current user. 178 * Shows unread entries for current user.
187 * 179 *
188 * @param Request $request 180 * @param int $page
189 * @param int $page
190 * 181 *
191 * @Route("/unread/list/{page}", name="unread", defaults={"page" = "1"}) 182 * @Route("/unread/list/{page}", name="unread", defaults={"page" = "1"})
192 * 183 *
@@ -205,8 +196,7 @@ class EntryController extends Controller
205 /** 196 /**
206 * Shows read entries for current user. 197 * Shows read entries for current user.
207 * 198 *
208 * @param Request $request 199 * @param int $page
209 * @param int $page
210 * 200 *
211 * @Route("/archive/list/{page}", name="archive", defaults={"page" = "1"}) 201 * @Route("/archive/list/{page}", name="archive", defaults={"page" = "1"})
212 * 202 *
@@ -220,8 +210,7 @@ class EntryController extends Controller
220 /** 210 /**
221 * Shows starred entries for current user. 211 * Shows starred entries for current user.
222 * 212 *
223 * @param Request $request 213 * @param int $page
224 * @param int $page
225 * 214 *
226 * @Route("/starred/list/{page}", name="starred", defaults={"page" = "1"}) 215 * @Route("/starred/list/{page}", name="starred", defaults={"page" = "1"})
227 * 216 *
@@ -233,9 +222,46 @@ class EntryController extends Controller
233 } 222 }
234 223
235 /** 224 /**
236 * Shows entry content. 225 * Shows untagged articles for current user.
226 *
227 * @param int $page
228 *
229 * @Route("/untagged/list/{page}", name="untagged", defaults={"page" = "1"})
237 * 230 *
238 * @param Entry $entry 231 * @return \Symfony\Component\HttpFoundation\Response
232 */
233 public function showUntaggedEntriesAction(Request $request, $page)
234 {
235 return $this->showEntries('untagged', $request, $page);
236 }
237
238 /**
239 * Shows random entry depending on the given type.
240 *
241 * @param string $type
242 *
243 * @Route("/{type}/random", name="random_entry", requirements={"type": "unread|starred|archive|untagged|all"})
244 *
245 * @return \Symfony\Component\HttpFoundation\RedirectResponse
246 */
247 public function redirectRandomEntryAction($type = 'all')
248 {
249 try {
250 $entry = $this->get('wallabag_core.entry_repository')
251 ->getRandomEntry($this->getUser()->getId(), $type);
252 } catch (NoResultException $e) {
253 $bag = $this->get('session')->getFlashBag();
254 $bag->clear();
255 $bag->add('notice', 'flashes.entry.notice.no_random_entry');
256
257 return $this->redirect($this->generateUrl($type));
258 }
259
260 return $this->redirect($this->generateUrl('view', ['id' => $entry->getId()]));
261 }
262
263 /**
264 * Shows entry content.
239 * 265 *
240 * @Route("/view/{id}", requirements={"id" = "\d+"}, name="view") 266 * @Route("/view/{id}", requirements={"id" = "\d+"}, name="view")
241 * 267 *
@@ -255,8 +281,6 @@ class EntryController extends Controller
255 * Reload an entry. 281 * Reload an entry.
256 * Refetch content from the website and make it readable again. 282 * Refetch content from the website and make it readable again.
257 * 283 *
258 * @param Entry $entry
259 *
260 * @Route("/reload/{id}", requirements={"id" = "\d+"}, name="reload_entry") 284 * @Route("/reload/{id}", requirements={"id" = "\d+"}, name="reload_entry")
261 * 285 *
262 * @return \Symfony\Component\HttpFoundation\RedirectResponse 286 * @return \Symfony\Component\HttpFoundation\RedirectResponse
@@ -289,9 +313,6 @@ class EntryController extends Controller
289 /** 313 /**
290 * Changes read status for an entry. 314 * Changes read status for an entry.
291 * 315 *
292 * @param Request $request
293 * @param Entry $entry
294 *
295 * @Route("/archive/{id}", requirements={"id" = "\d+"}, name="archive_entry") 316 * @Route("/archive/{id}", requirements={"id" = "\d+"}, name="archive_entry")
296 * 317 *
297 * @return \Symfony\Component\HttpFoundation\RedirectResponse 318 * @return \Symfony\Component\HttpFoundation\RedirectResponse
@@ -321,9 +342,6 @@ class EntryController extends Controller
321 /** 342 /**
322 * Changes starred status for an entry. 343 * Changes starred status for an entry.
323 * 344 *
324 * @param Request $request
325 * @param Entry $entry
326 *
327 * @Route("/star/{id}", requirements={"id" = "\d+"}, name="star_entry") 345 * @Route("/star/{id}", requirements={"id" = "\d+"}, name="star_entry")
328 * 346 *
329 * @return \Symfony\Component\HttpFoundation\RedirectResponse 347 * @return \Symfony\Component\HttpFoundation\RedirectResponse
@@ -354,8 +372,6 @@ class EntryController extends Controller
354 /** 372 /**
355 * Deletes entry and redirect to the homepage or the last viewed page. 373 * Deletes entry and redirect to the homepage or the last viewed page.
356 * 374 *
357 * @param Entry $entry
358 *
359 * @Route("/delete/{id}", requirements={"id" = "\d+"}, name="delete_entry") 375 * @Route("/delete/{id}", requirements={"id" = "\d+"}, name="delete_entry")
360 * 376 *
361 * @return \Symfony\Component\HttpFoundation\RedirectResponse 377 * @return \Symfony\Component\HttpFoundation\RedirectResponse
@@ -396,8 +412,6 @@ class EntryController extends Controller
396 /** 412 /**
397 * Get public URL for entry (and generate it if necessary). 413 * Get public URL for entry (and generate it if necessary).
398 * 414 *
399 * @param Entry $entry
400 *
401 * @Route("/share/{id}", requirements={"id" = "\d+"}, name="share") 415 * @Route("/share/{id}", requirements={"id" = "\d+"}, name="share")
402 * 416 *
403 * @return \Symfony\Component\HttpFoundation\Response 417 * @return \Symfony\Component\HttpFoundation\Response
@@ -422,8 +436,6 @@ class EntryController extends Controller
422 /** 436 /**
423 * Disable public sharing for an entry. 437 * Disable public sharing for an entry.
424 * 438 *
425 * @param Entry $entry
426 *
427 * @Route("/share/delete/{id}", requirements={"id" = "\d+"}, name="delete_share") 439 * @Route("/share/delete/{id}", requirements={"id" = "\d+"}, name="delete_share")
428 * 440 *
429 * @return \Symfony\Component\HttpFoundation\Response 441 * @return \Symfony\Component\HttpFoundation\Response
@@ -446,8 +458,6 @@ class EntryController extends Controller
446 /** 458 /**
447 * Ability to view a content publicly. 459 * Ability to view a content publicly.
448 * 460 *
449 * @param Entry $entry
450 *
451 * @Route("/share/{uid}", requirements={"uid" = ".+"}, name="share_entry") 461 * @Route("/share/{uid}", requirements={"uid" = ".+"}, name="share_entry")
452 * @Cache(maxage="25200", smaxage="25200", public=true) 462 * @Cache(maxage="25200", smaxage="25200", public=true)
453 * 463 *
@@ -466,60 +476,11 @@ class EntryController extends Controller
466 } 476 }
467 477
468 /** 478 /**
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 479 * Global method to retrieve entries depending on the given type
518 * It returns the response to be send. 480 * It returns the response to be send.
519 * 481 *
520 * @param string $type Entries type: unread, starred or archive 482 * @param string $type Entries type: unread, starred or archive
521 * @param Request $request 483 * @param int $page
522 * @param int $page
523 * 484 *
524 * @return \Symfony\Component\HttpFoundation\Response 485 * @return \Symfony\Component\HttpFoundation\Response
525 */ 486 */
@@ -532,11 +493,9 @@ class EntryController extends Controller
532 switch ($type) { 493 switch ($type) {
533 case 'search': 494 case 'search':
534 $qb = $repository->getBuilderForSearchByUser($this->getUser()->getId(), $searchTerm, $currentRoute); 495 $qb = $repository->getBuilderForSearchByUser($this->getUser()->getId(), $searchTerm, $currentRoute);
535
536 break; 496 break;
537 case 'untagged': 497 case 'untagged':
538 $qb = $repository->getBuilderForUntaggedByUser($this->getUser()->getId()); 498 $qb = $repository->getBuilderForUntaggedByUser($this->getUser()->getId());
539
540 break; 499 break;
541 case 'starred': 500 case 'starred':
542 $qb = $repository->getBuilderForStarredByUser($this->getUser()->getId()); 501 $qb = $repository->getBuilderForStarredByUser($this->getUser()->getId());
@@ -576,6 +535,9 @@ class EntryController extends Controller
576 } 535 }
577 } 536 }
578 537
538 $nbEntriesUntagged = $this->get('wallabag_core.entry_repository')
539 ->countUntaggedEntriesByUser($this->getUser()->getId());
540
579 return $this->render( 541 return $this->render(
580 'WallabagCoreBundle:Entry:entries.html.twig', [ 542 'WallabagCoreBundle:Entry:entries.html.twig', [
581 'form' => $form->createView(), 543 'form' => $form->createView(),
@@ -583,14 +545,45 @@ class EntryController extends Controller
583 'currentPage' => $page, 545 'currentPage' => $page,
584 'searchTerm' => $searchTerm, 546 'searchTerm' => $searchTerm,
585 'isFiltered' => $form->isSubmitted(), 547 'isFiltered' => $form->isSubmitted(),
548 'nbEntriesUntagged' => $nbEntriesUntagged,
586 ] 549 ]
587 ); 550 );
588 } 551 }
589 552
590 /** 553 /**
591 * Check if the logged user can manage the given entry. 554 * Fetch content and update entry.
555 * In case it fails, $entry->getContent will return an error message.
592 * 556 *
593 * @param Entry $entry 557 * @param string $prefixMessage Should be the translation key: entry_saved or entry_reloaded
558 */
559 private function updateEntry(Entry $entry, $prefixMessage = 'entry_saved')
560 {
561 $message = 'flashes.entry.notice.' . $prefixMessage;
562
563 try {
564 $this->get('wallabag_core.content_proxy')->updateEntry($entry, $entry->getUrl());
565 } catch (\Exception $e) {
566 $this->get('logger')->error('Error while saving an entry', [
567 'exception' => $e,
568 'entry' => $entry,
569 ]);
570
571 $message = 'flashes.entry.notice.' . $prefixMessage . '_failed';
572 }
573
574 if (empty($entry->getDomainName())) {
575 $this->get('wallabag_core.content_proxy')->setEntryDomainName($entry);
576 }
577
578 if (empty($entry->getTitle())) {
579 $this->get('wallabag_core.content_proxy')->setDefaultEntryTitle($entry);
580 }
581
582 $this->get('session')->getFlashBag()->add('notice', $message);
583 }
584
585 /**
586 * Check if the logged user can manage the given entry.
594 */ 587 */
595 private function checkUserAction(Entry $entry) 588 private function checkUserAction(Entry $entry)
596 { 589 {
@@ -602,8 +595,6 @@ class EntryController extends Controller
602 /** 595 /**
603 * Check for existing entry, if it exists, redirect to it with a message. 596 * Check for existing entry, if it exists, redirect to it with a message.
604 * 597 *
605 * @param Entry $entry
606 *
607 * @return Entry|bool 598 * @return Entry|bool
608 */ 599 */
609 private function checkIfEntryAlreadyExists(Entry $entry) 600 private function checkIfEntryAlreadyExists(Entry $entry)
diff --git a/src/Wallabag/CoreBundle/Controller/ExportController.php b/src/Wallabag/CoreBundle/Controller/ExportController.php
index 7ca89239..282fd733 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/**
@@ -17,7 +17,6 @@ class ExportController extends Controller
17 /** 17 /**
18 * Gets one entry content. 18 * Gets one entry content.
19 * 19 *
20 * @param Entry $entry
21 * @param string $format 20 * @param string $format
22 * 21 *
23 * @Route("/export/{id}.{format}", name="export_entry", requirements={ 22 * @Route("/export/{id}.{format}", name="export_entry", requirements={
@@ -58,6 +57,7 @@ class ExportController extends Controller
58 $method = ucfirst($category); 57 $method = ucfirst($category);
59 $methodBuilder = 'getBuilderFor' . $method . 'ByUser'; 58 $methodBuilder = 'getBuilderFor' . $method . 'ByUser';
60 $repository = $this->get('wallabag_core.entry_repository'); 59 $repository = $this->get('wallabag_core.entry_repository');
60 $title = $method;
61 61
62 if ('tag_entries' === $category) { 62 if ('tag_entries' === $category) {
63 $tag = $this->get('wallabag_core.tag_repository')->findOneBySlug($request->query->get('tag')); 63 $tag = $this->get('wallabag_core.tag_repository')->findOneBySlug($request->query->get('tag'));
@@ -66,6 +66,8 @@ class ExportController extends Controller
66 $this->getUser()->getId(), 66 $this->getUser()->getId(),
67 $tag->getId() 67 $tag->getId()
68 ); 68 );
69
70 $title = 'Tag ' . $tag->getLabel();
69 } else { 71 } else {
70 $entries = $repository 72 $entries = $repository
71 ->$methodBuilder($this->getUser()->getId()) 73 ->$methodBuilder($this->getUser()->getId())
@@ -76,7 +78,7 @@ class ExportController extends Controller
76 try { 78 try {
77 return $this->get('wallabag_core.helper.entries_export') 79 return $this->get('wallabag_core.helper.entries_export')
78 ->setEntries($entries) 80 ->setEntries($entries)
79 ->updateTitle($method) 81 ->updateTitle($title)
80 ->updateAuthor($method) 82 ->updateAuthor($method)
81 ->exportAs($format); 83 ->exportAs($format);
82 } catch (\InvalidArgumentException $e) { 84 } catch (\InvalidArgumentException $e) {
diff --git a/src/Wallabag/CoreBundle/Controller/RssController.php b/src/Wallabag/CoreBundle/Controller/FeedController.php
index 848bb814..95c3427b 100644
--- a/src/Wallabag/CoreBundle/Controller/RssController.php
+++ b/src/Wallabag/CoreBundle/Controller/FeedController.php
@@ -7,86 +7,94 @@ 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 $page
25 * 27 *
26 * @return \Symfony\Component\HttpFoundation\Response 28 * @return \Symfony\Component\HttpFoundation\Response
27 */ 29 */
28 public function showUnreadRSSAction(Request $request, User $user) 30 public function showUnreadFeedAction(User $user, $page)
29 { 31 {
30 return $this->showEntries('unread', $user, $request->query->get('page', 1)); 32 return $this->showEntries('unread', $user, $page);
31 } 33 }
32 34
33 /** 35 /**
34 * Shows read entries for current user. 36 * Shows read entries for current user.
35 * 37 *
36 * @Route("/{username}/{token}/archive.xml", name="archive_rss", defaults={"_format"="xml"}) 38 * @Route("/feed/{username}/{token}/archive/{page}", name="archive_feed", defaults={"page"=1, "_format"="xml"})
37 * @ParamConverter("user", class="WallabagUserBundle:User", converter="username_rsstoken_converter") 39 *
40 * @ParamConverter("user", class="WallabagUserBundle:User", converter="username_feed_token_converter")
41 *
42 * @param $page
38 * 43 *
39 * @return \Symfony\Component\HttpFoundation\Response 44 * @return \Symfony\Component\HttpFoundation\Response
40 */ 45 */
41 public function showArchiveRSSAction(Request $request, User $user) 46 public function showArchiveFeedAction(User $user, $page)
42 { 47 {
43 return $this->showEntries('archive', $user, $request->query->get('page', 1)); 48 return $this->showEntries('archive', $user, $page);
44 } 49 }
45 50
46 /** 51 /**
47 * Shows starred entries for current user. 52 * Shows starred entries for current user.
48 * 53 *
49 * @Route("/{username}/{token}/starred.xml", name="starred_rss", defaults={"_format"="xml"}) 54 * @Route("/feed/{username}/{token}/starred/{page}", name="starred_feed", defaults={"page"=1, "_format"="xml"})
50 * @ParamConverter("user", class="WallabagUserBundle:User", converter="username_rsstoken_converter") 55 *
56 * @ParamConverter("user", class="WallabagUserBundle:User", converter="username_feed_token_converter")
57 *
58 * @param $page
51 * 59 *
52 * @return \Symfony\Component\HttpFoundation\Response 60 * @return \Symfony\Component\HttpFoundation\Response
53 */ 61 */
54 public function showStarredRSSAction(Request $request, User $user) 62 public function showStarredFeedAction(User $user, $page)
55 { 63 {
56 return $this->showEntries('starred', $user, $request->query->get('page', 1)); 64 return $this->showEntries('starred', $user, $page);
57 } 65 }
58 66
59 /** 67 /**
60 * Shows all entries for current user. 68 * Shows all entries for current user.
61 * 69 *
62 * @Route("/{username}/{token}/all.xml", name="all_rss", defaults={"_format"="xml"}) 70 * @Route("/feed/{username}/{token}/all/{page}", name="all_feed", defaults={"page"=1, "_format"="xml"})
63 * @ParamConverter("user", class="WallabagUserBundle:User", converter="username_rsstoken_converter") 71 *
72 * @ParamConverter("user", class="WallabagUserBundle:User", converter="username_feed_token_converter")
64 * 73 *
65 * @return \Symfony\Component\HttpFoundation\Response 74 * @return \Symfony\Component\HttpFoundation\Response
66 */ 75 */
67 public function showAllRSSAction(Request $request, User $user) 76 public function showAllFeedAction(User $user, $page)
68 { 77 {
69 return $this->showEntries('all', $user, $request->query->get('page', 1)); 78 return $this->showEntries('all', $user, $page);
70 } 79 }
71 80
72 /** 81 /**
73 * Shows entries associated to a tag for current user. 82 * Shows entries associated to a tag for current user.
74 * 83 *
75 * @Route("/{username}/{token}/tags/{slug}.xml", name="tag_rss", defaults={"_format"="xml"}) 84 * @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") 85 *
86 * @ParamConverter("user", class="WallabagUserBundle:User", converter="username_feed_token_converter")
77 * @ParamConverter("tag", options={"mapping": {"slug": "slug"}}) 87 * @ParamConverter("tag", options={"mapping": {"slug": "slug"}})
78 * 88 *
79 * @return \Symfony\Component\HttpFoundation\Response 89 * @return \Symfony\Component\HttpFoundation\Response
80 */ 90 */
81 public function showTagsAction(Request $request, User $user, Tag $tag) 91 public function showTagsFeedAction(User $user, Tag $tag, $page)
82 { 92 {
83 $page = $request->query->get('page', 1);
84
85 $url = $this->generateUrl( 93 $url = $this->generateUrl(
86 'tag_rss', 94 'tag_feed',
87 [ 95 [
88 'username' => $user->getUsername(), 96 'username' => $user->getUsername(),
89 'token' => $user->getConfig()->getRssToken(), 97 'token' => $user->getConfig()->getFeedToken(),
90 'slug' => $tag->getSlug(), 98 'slug' => $tag->getSlug(),
91 ], 99 ],
92 UrlGeneratorInterface::ABSOLUTE_URL 100 UrlGeneratorInterface::ABSOLUTE_URL
@@ -119,12 +127,15 @@ class RssController extends Controller
119 return $this->render( 127 return $this->render(
120 '@WallabagCore/themes/common/Entry/entries.xml.twig', 128 '@WallabagCore/themes/common/Entry/entries.xml.twig',
121 [ 129 [
122 'url_html' => $this->generateUrl('tag_entries', ['slug' => $tag->getSlug()], UrlGeneratorInterface::ABSOLUTE_URL), 130 'type' => 'tag',
123 'type' => 'tag (' . $tag->getLabel() . ')',
124 'url' => $url, 131 'url' => $url,
125 'entries' => $entries, 132 'entries' => $entries,
133 'user' => $user->getUsername(),
134 'domainName' => $this->getParameter('domain_name'),
135 'version' => $this->getParameter('wallabag_core.version'),
136 'tag' => $tag->getSlug(),
126 ], 137 ],
127 new Response('', 200, ['Content-Type' => 'application/rss+xml']) 138 new Response('', 200, ['Content-Type' => 'application/atom+xml'])
128 ); 139 );
129 } 140 }
130 141
@@ -133,7 +144,6 @@ class RssController extends Controller
133 * It returns the response to be send. 144 * It returns the response to be send.
134 * 145 *
135 * @param string $type Entries type: unread, starred or archive 146 * @param string $type Entries type: unread, starred or archive
136 * @param User $user
137 * @param int $page 147 * @param int $page
138 * 148 *
139 * @return \Symfony\Component\HttpFoundation\Response 149 * @return \Symfony\Component\HttpFoundation\Response
@@ -162,14 +172,14 @@ class RssController extends Controller
162 $pagerAdapter = new DoctrineORMAdapter($qb->getQuery(), true, false); 172 $pagerAdapter = new DoctrineORMAdapter($qb->getQuery(), true, false);
163 $entries = new Pagerfanta($pagerAdapter); 173 $entries = new Pagerfanta($pagerAdapter);
164 174
165 $perPage = $user->getConfig()->getRssLimit() ?: $this->getParameter('wallabag_core.rss_limit'); 175 $perPage = $user->getConfig()->getFeedLimit() ?: $this->getParameter('wallabag_core.feed_limit');
166 $entries->setMaxPerPage($perPage); 176 $entries->setMaxPerPage($perPage);
167 177
168 $url = $this->generateUrl( 178 $url = $this->generateUrl(
169 $type . '_rss', 179 $type . '_feed',
170 [ 180 [
171 'username' => $user->getUsername(), 181 'username' => $user->getUsername(),
172 'token' => $user->getConfig()->getRssToken(), 182 'token' => $user->getConfig()->getFeedToken(),
173 ], 183 ],
174 UrlGeneratorInterface::ABSOLUTE_URL 184 UrlGeneratorInterface::ABSOLUTE_URL
175 ); 185 );
@@ -178,19 +188,19 @@ class RssController extends Controller
178 $entries->setCurrentPage((int) $page); 188 $entries->setCurrentPage((int) $page);
179 } catch (OutOfRangeCurrentPageException $e) { 189 } catch (OutOfRangeCurrentPageException $e) {
180 if ($page > 1) { 190 if ($page > 1) {
181 return $this->redirect($url . '?page=' . $entries->getNbPages(), 302); 191 return $this->redirect($url . '/' . $entries->getNbPages());
182 } 192 }
183 } 193 }
184 194
185 return $this->render( 195 return $this->render('@WallabagCore/themes/common/Entry/entries.xml.twig', [
186 '@WallabagCore/themes/common/Entry/entries.xml.twig', 196 'type' => $type,
187 [ 197 'url' => $url,
188 'url_html' => $this->generateUrl($type, [], UrlGeneratorInterface::ABSOLUTE_URL), 198 'entries' => $entries,
189 'type' => $type, 199 'user' => $user->getUsername(),
190 'url' => $url, 200 'domainName' => $this->getParameter('domain_name'),
191 'entries' => $entries, 201 'version' => $this->getParameter('wallabag_core.version'),
192 ], 202 ],
193 new Response('', 200, ['Content-Type' => 'application/rss+xml']) 203 new Response('', 200, ['Content-Type' => 'application/atom+xml'])
194 ); 204 );
195 } 205 }
196} 206}
diff --git a/src/Wallabag/CoreBundle/Controller/SiteCredentialController.php b/src/Wallabag/CoreBundle/Controller/SiteCredentialController.php
index 548de744..4320c5ff 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,10 +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 *
42 * @param Request $request
43 * 38 *
44 * @return \Symfony\Component\HttpFoundation\Response 39 * @return \Symfony\Component\HttpFoundation\Response
45 */ 40 */
@@ -77,11 +72,7 @@ class SiteCredentialController extends Controller
77 /** 72 /**
78 * Displays a form to edit an existing site credential entity. 73 * Displays a form to edit an existing site credential entity.
79 * 74 *
80 * @Route("/{id}/edit", name="site_credentials_edit") 75 * @Route("/{id}/edit", name="site_credentials_edit", methods={"GET", "POST"})
81 * @Method({"GET", "POST"})
82 *
83 * @param Request $request
84 * @param SiteCredential $siteCredential
85 * 76 *
86 * @return \Symfony\Component\HttpFoundation\Response 77 * @return \Symfony\Component\HttpFoundation\Response
87 */ 78 */
@@ -121,11 +112,7 @@ class SiteCredentialController extends Controller
121 /** 112 /**
122 * Deletes a site credential entity. 113 * Deletes a site credential entity.
123 * 114 *
124 * @Route("/{id}", name="site_credentials_delete") 115 * @Route("/{id}", name="site_credentials_delete", methods={"DELETE"})
125 * @Method("DELETE")
126 *
127 * @param Request $request
128 * @param SiteCredential $siteCredential
129 * 116 *
130 * @return \Symfony\Component\HttpFoundation\RedirectResponse 117 * @return \Symfony\Component\HttpFoundation\RedirectResponse
131 */ 118 */
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..a6ad131f 100644
--- a/src/Wallabag/CoreBundle/Controller/TagController.php
+++ b/src/Wallabag/CoreBundle/Controller/TagController.php
@@ -5,19 +5,17 @@ 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{
17 /** 18 /**
18 * @param Request $request
19 * @param Entry $entry
20 *
21 * @Route("/new-tag/{entry}", requirements={"entry" = "\d+"}, name="new_tag") 19 * @Route("/new-tag/{entry}", requirements={"entry" = "\d+"}, name="new_tag")
22 * 20 *
23 * @return \Symfony\Component\HttpFoundation\Response 21 * @return \Symfony\Component\HttpFoundation\Response
@@ -86,14 +84,22 @@ class TagController extends Controller
86 { 84 {
87 $tags = $this->get('wallabag_core.tag_repository') 85 $tags = $this->get('wallabag_core.tag_repository')
88 ->findAllFlatTagsWithNbEntries($this->getUser()->getId()); 86 ->findAllFlatTagsWithNbEntries($this->getUser()->getId());
87 $nbEntriesUntagged = $this->get('wallabag_core.entry_repository')
88 ->countUntaggedEntriesByUser($this->getUser()->getId());
89
90 $renameForms = [];
91 foreach ($tags as $tag) {
92 $renameForms[$tag['id']] = $this->createForm(RenameTagType::class, new Tag())->createView();
93 }
89 94
90 return $this->render('WallabagCoreBundle:Tag:tags.html.twig', [ 95 return $this->render('WallabagCoreBundle:Tag:tags.html.twig', [
91 'tags' => $tags, 96 'tags' => $tags,
97 'renameForms' => $renameForms,
98 'nbEntriesUntagged' => $nbEntriesUntagged,
92 ]); 99 ]);
93 } 100 }
94 101
95 /** 102 /**
96 * @param Tag $tag
97 * @param int $page 103 * @param int $page
98 * 104 *
99 * @Route("/tag/list/{slug}/{page}", name="tag_entries", defaults={"page" = "1"}) 105 * @Route("/tag/list/{slug}/{page}", name="tag_entries", defaults={"page" = "1"})
@@ -130,4 +136,45 @@ class TagController extends Controller
130 'tag' => $tag, 136 'tag' => $tag,
131 ]); 137 ]);
132 } 138 }
139
140 /**
141 * Rename a given tag with a new label
142 * Create a new tag with the new name and drop the old one.
143 *
144 * @Route("/tag/rename/{slug}", name="tag_rename")
145 * @ParamConverter("tag", options={"mapping": {"slug": "slug"}})
146 *
147 * @return \Symfony\Component\HttpFoundation\Response
148 */
149 public function renameTagAction(Tag $tag, Request $request)
150 {
151 $form = $this->createForm(RenameTagType::class, new Tag());
152 $form->handleRequest($request);
153
154 if ($form->isSubmitted() && $form->isValid()) {
155 $entries = $this->get('wallabag_core.entry_repository')->findAllByTagId(
156 $this->getUser()->getId(),
157 $tag->getId()
158 );
159 foreach ($entries as $entry) {
160 $this->get('wallabag_core.tags_assigner')->assignTagsToEntry(
161 $entry,
162 $form->get('label')->getData()
163 );
164 $entry->removeTag($tag);
165 }
166
167 $em = $this->getDoctrine()->getManager();
168 $em->flush();
169 }
170
171 $this->get('session')->getFlashBag()->add(
172 'notice',
173 'flashes.tag.notice.tag_renamed'
174 );
175
176 $redirectUrl = $this->get('wallabag_core.helper.redirect')->to($request->headers->get('referer'), '', true);
177
178 return $this->redirect($redirectUrl);
179 }
133} 180}
diff --git a/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadConfigData.php b/src/Wallabag/CoreBundle/DataFixtures/ConfigFixtures.php
index 3d4d5def..5e914965 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}
@@ -18,7 +19,7 @@ class LoadConfigData extends AbstractFixture implements OrderedFixtureInterface
18 19
19 $adminConfig->setTheme('material'); 20 $adminConfig->setTheme('material');
20 $adminConfig->setItemsPerPage(30); 21 $adminConfig->setItemsPerPage(30);
21 $adminConfig->setReadingSpeed(1); 22 $adminConfig->setReadingSpeed(200);
22 $adminConfig->setLanguage('en'); 23 $adminConfig->setLanguage('en');
23 $adminConfig->setPocketConsumerKey('xxxxx'); 24 $adminConfig->setPocketConsumerKey('xxxxx');
24 $adminConfig->setActionMarkAsRead(0); 25 $adminConfig->setActionMarkAsRead(0);
@@ -31,7 +32,7 @@ class LoadConfigData extends AbstractFixture implements OrderedFixtureInterface
31 $bobConfig = new Config($this->getReference('bob-user')); 32 $bobConfig = new Config($this->getReference('bob-user'));
32 $bobConfig->setTheme('default'); 33 $bobConfig->setTheme('default');
33 $bobConfig->setItemsPerPage(10); 34 $bobConfig->setItemsPerPage(10);
34 $bobConfig->setReadingSpeed(1); 35 $bobConfig->setReadingSpeed(200);
35 $bobConfig->setLanguage('fr'); 36 $bobConfig->setLanguage('fr');
36 $bobConfig->setPocketConsumerKey(null); 37 $bobConfig->setPocketConsumerKey(null);
37 $bobConfig->setActionMarkAsRead(1); 38 $bobConfig->setActionMarkAsRead(1);
@@ -44,7 +45,7 @@ class LoadConfigData extends AbstractFixture implements OrderedFixtureInterface
44 $emptyConfig = new Config($this->getReference('empty-user')); 45 $emptyConfig = new Config($this->getReference('empty-user'));
45 $emptyConfig->setTheme('material'); 46 $emptyConfig->setTheme('material');
46 $emptyConfig->setItemsPerPage(10); 47 $emptyConfig->setItemsPerPage(10);
47 $emptyConfig->setReadingSpeed(1); 48 $emptyConfig->setReadingSpeed(200);
48 $emptyConfig->setLanguage('en'); 49 $emptyConfig->setLanguage('en');
49 $emptyConfig->setPocketConsumerKey(null); 50 $emptyConfig->setPocketConsumerKey(null);
50 $emptyConfig->setActionMarkAsRead(0); 51 $emptyConfig->setActionMarkAsRead(0);
@@ -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/EntryFixtures.php b/src/Wallabag/CoreBundle/DataFixtures/EntryFixtures.php
new file mode 100644
index 00000000..024fcfdc
--- /dev/null
+++ b/src/Wallabag/CoreBundle/DataFixtures/EntryFixtures.php
@@ -0,0 +1,138 @@
1<?php
2
3namespace Wallabag\CoreBundle\DataFixtures;
4
5use Doctrine\Bundle\FixturesBundle\Fixture;
6use Doctrine\Common\DataFixtures\DependentFixtureInterface;
7use Doctrine\Common\Persistence\ObjectManager;
8use Wallabag\CoreBundle\Entity\Entry;
9use Wallabag\UserBundle\DataFixtures\UserFixtures;
10
11class EntryFixtures extends Fixture implements DependentFixtureInterface
12{
13 /**
14 * {@inheritdoc}
15 */
16 public function load(ObjectManager $manager)
17 {
18 $entries = [
19 'entry1' => [
20 'user' => 'admin-user',
21 'url' => 'http://0.0.0.0/entry1',
22 'reading_time' => 11,
23 'domain' => 'domain.io',
24 'mime' => 'text/html',
25 'title' => 'test title entry1',
26 'content' => 'This is my content /o/',
27 'language' => 'en',
28 'tags' => ['foo-tag', 'baz-tag'],
29 ],
30 'entry2' => [
31 'user' => 'admin-user',
32 'url' => 'http://0.0.0.0/entry2',
33 'reading_time' => 1,
34 'domain' => 'domain.io',
35 'mime' => 'text/html',
36 'title' => 'test title entry2',
37 'content' => 'This is my content /o/',
38 'origin' => 'ftp://oneftp.tld',
39 'language' => 'fr',
40 ],
41 'entry3' => [
42 'user' => 'bob-user',
43 'url' => 'http://0.0.0.0/entry3',
44 'reading_time' => 1,
45 'domain' => 'domain.io',
46 'mime' => 'text/html',
47 'title' => 'test title entry3',
48 'content' => 'This is my content /o/',
49 'language' => 'en',
50 'tags' => ['foo-tag', 'bar-tag', 'bob-tag'],
51 ],
52 'entry4' => [
53 'user' => 'admin-user',
54 'url' => 'http://0.0.0.0/entry4',
55 'reading_time' => 12,
56 'domain' => 'domain.io',
57 'mime' => 'text/html',
58 'title' => 'test title entry4',
59 'content' => 'This is my content /o/',
60 'language' => 'en',
61 'tags' => ['foo-tag', 'bar-tag'],
62 ],
63 'entry5' => [
64 'user' => 'admin-user',
65 'url' => 'http://0.0.0.0/entry5',
66 'reading_time' => 12,
67 'domain' => 'domain.io',
68 'mime' => 'text/html',
69 'title' => 'test title entry5',
70 'content' => 'This is my content /o/',
71 'language' => 'fr',
72 'starred' => true,
73 'preview' => 'http://0.0.0.0/image.jpg',
74 ],
75 'entry6' => [
76 'user' => 'admin-user',
77 'url' => 'http://0.0.0.0/entry6',
78 'reading_time' => 12,
79 'domain' => 'domain.io',
80 'mime' => 'text/html',
81 'title' => 'test title entry6',
82 'content' => 'This is my content /o/',
83 'language' => 'de',
84 'archived' => true,
85 'tags' => ['bar-tag'],
86 ],
87 ];
88
89 foreach ($entries as $reference => $item) {
90 $entry = new Entry($this->getReference($item['user']));
91 $entry->setUrl($item['url']);
92 $entry->setReadingTime($item['reading_time']);
93 $entry->setDomainName($item['domain']);
94 $entry->setMimetype($item['mime']);
95 $entry->setTitle($item['title']);
96 $entry->setContent($item['content']);
97 $entry->setLanguage($item['language']);
98
99 if (isset($item['tags'])) {
100 foreach ($item['tags'] as $tag) {
101 $entry->addTag($this->getReference($tag));
102 }
103 }
104
105 if (isset($item['origin'])) {
106 $entry->setOriginUrl($item['origin']);
107 }
108
109 if (isset($item['starred'])) {
110 $entry->setStarred($item['starred']);
111 }
112
113 if (isset($item['archived'])) {
114 $entry->setArchived($item['archived']);
115 }
116
117 if (isset($item['preview'])) {
118 $entry->setPreviewPicture($item['preview']);
119 }
120
121 $manager->persist($entry);
122 $this->addReference($reference, $entry);
123 }
124
125 $manager->flush();
126 }
127
128 /**
129 * {@inheritdoc}
130 */
131 public function getDependencies()
132 {
133 return [
134 UserFixtures::class,
135 TagFixtures::class,
136 ];
137 }
138}
diff --git a/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadSettingData.php b/src/Wallabag/CoreBundle/DataFixtures/InternalSettingFixtures.php
index 3fe88e7f..b052d1d5 100644
--- a/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadSettingData.php
+++ b/src/Wallabag/CoreBundle/DataFixtures/InternalSettingFixtures.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 Doctrine\Bundle\FixturesBundle\Fixture;
6use Doctrine\Common\DataFixtures\AbstractFixture;
7use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
8use Doctrine\Common\Persistence\ObjectManager; 6use Doctrine\Common\Persistence\ObjectManager;
9use Symfony\Component\DependencyInjection\ContainerAwareInterface; 7use Symfony\Component\DependencyInjection\ContainerAwareInterface;
10use Symfony\Component\DependencyInjection\ContainerInterface; 8use Symfony\Component\DependencyInjection\ContainerInterface;
9use Wallabag\CoreBundle\Entity\InternalSetting;
11 10
12class LoadSettingData extends AbstractFixture implements OrderedFixtureInterface, ContainerAwareInterface 11class InternalSettingFixtures extends Fixture implements ContainerAwareInterface
13{ 12{
14 /** 13 /**
15 * @var ContainerInterface 14 * @var ContainerInterface
@@ -27,7 +26,7 @@ class LoadSettingData extends AbstractFixture implements OrderedFixtureInterface
27 public function load(ObjectManager $manager) 26 public function load(ObjectManager $manager)
28 { 27 {
29 foreach ($this->container->getParameter('wallabag_core.default_internal_settings') as $setting) { 28 foreach ($this->container->getParameter('wallabag_core.default_internal_settings') as $setting) {
30 $newSetting = new Setting(); 29 $newSetting = new InternalSetting();
31 $newSetting->setName($setting['name']); 30 $newSetting->setName($setting['name']);
32 $newSetting->setValue($setting['value']); 31 $newSetting->setValue($setting['value']);
33 $newSetting->setSection($setting['section']); 32 $newSetting->setSection($setting['section']);
@@ -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/LoadEntryData.php b/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadEntryData.php
deleted file mode 100644
index 0e1510a2..00000000
--- a/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadEntryData.php
+++ /dev/null
@@ -1,119 +0,0 @@
1<?php
2
3namespace Wallabag\CoreBundle\DataFixtures\ORM;
4
5use Doctrine\Common\DataFixtures\AbstractFixture;
6use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
7use Doctrine\Common\Persistence\ObjectManager;
8use Wallabag\CoreBundle\Entity\Entry;
9
10class LoadEntryData extends AbstractFixture implements OrderedFixtureInterface
11{
12 /**
13 * {@inheritdoc}
14 */
15 public function load(ObjectManager $manager)
16 {
17 $entry1 = new Entry($this->getReference('admin-user'));
18 $entry1->setUrl('http://0.0.0.0/entry1');
19 $entry1->setReadingTime(11);
20 $entry1->setDomainName('domain.io');
21 $entry1->setMimetype('text/html');
22 $entry1->setTitle('test title entry1');
23 $entry1->setContent('This is my content /o/');
24 $entry1->setLanguage('en');
25
26 $entry1->addTag($this->getReference('foo-tag'));
27 $entry1->addTag($this->getReference('baz-tag'));
28
29 $manager->persist($entry1);
30
31 $this->addReference('entry1', $entry1);
32
33 $entry2 = new Entry($this->getReference('admin-user'));
34 $entry2->setUrl('http://0.0.0.0/entry2');
35 $entry2->setReadingTime(1);
36 $entry2->setDomainName('domain.io');
37 $entry2->setMimetype('text/html');
38 $entry2->setTitle('test title entry2');
39 $entry2->setContent('This is my content /o/');
40 $entry2->setOriginUrl('ftp://oneftp.tld');
41 $entry2->setLanguage('fr');
42
43 $manager->persist($entry2);
44
45 $this->addReference('entry2', $entry2);
46
47 $entry3 = new Entry($this->getReference('bob-user'));
48 $entry3->setUrl('http://0.0.0.0/entry3');
49 $entry3->setReadingTime(1);
50 $entry3->setDomainName('domain.io');
51 $entry3->setMimetype('text/html');
52 $entry3->setTitle('test title entry3');
53 $entry3->setContent('This is my content /o/');
54 $entry3->setLanguage('en');
55
56 $entry3->addTag($this->getReference('foo-tag'));
57 $entry3->addTag($this->getReference('bar-tag'));
58
59 $manager->persist($entry3);
60
61 $this->addReference('entry3', $entry3);
62
63 $entry4 = new Entry($this->getReference('admin-user'));
64 $entry4->setUrl('http://0.0.0.0/entry4');
65 $entry4->setReadingTime(12);
66 $entry4->setDomainName('domain.io');
67 $entry4->setMimetype('text/html');
68 $entry4->setTitle('test title entry4');
69 $entry4->setContent('This is my content /o/');
70 $entry4->setLanguage('en');
71
72 $entry4->addTag($this->getReference('foo-tag'));
73 $entry4->addTag($this->getReference('bar-tag'));
74
75 $manager->persist($entry4);
76
77 $this->addReference('entry4', $entry4);
78
79 $entry5 = new Entry($this->getReference('admin-user'));
80 $entry5->setUrl('http://0.0.0.0/entry5');
81 $entry5->setReadingTime(12);
82 $entry5->setDomainName('domain.io');
83 $entry5->setMimetype('text/html');
84 $entry5->setTitle('test title entry5');
85 $entry5->setContent('This is my content /o/');
86 $entry5->setStarred(true);
87 $entry5->setLanguage('fr');
88 $entry5->setPreviewPicture('http://0.0.0.0/image.jpg');
89
90 $manager->persist($entry5);
91
92 $this->addReference('entry5', $entry5);
93
94 $entry6 = new Entry($this->getReference('admin-user'));
95 $entry6->setUrl('http://0.0.0.0/entry6');
96 $entry6->setReadingTime(12);
97 $entry6->setDomainName('domain.io');
98 $entry6->setMimetype('text/html');
99 $entry6->setTitle('test title entry6');
100 $entry6->setContent('This is my content /o/');
101 $entry6->setArchived(true);
102 $entry6->setLanguage('de');
103 $entry6->addTag($this->getReference('bar-tag'));
104
105 $manager->persist($entry6);
106
107 $this->addReference('entry6', $entry6);
108
109 $manager->flush();
110 }
111
112 /**
113 * {@inheritdoc}
114 */
115 public function getOrder()
116 {
117 return 30;
118 }
119}
diff --git a/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadSiteCredentialData.php b/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadSiteCredentialData.php
deleted file mode 100644
index 866f55a4..00000000
--- a/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadSiteCredentialData.php
+++ /dev/null
@@ -1,34 +0,0 @@
1<?php
2
3namespace Wallabag\CoreBundle\DataFixtures\ORM;
4
5use Doctrine\Common\DataFixtures\AbstractFixture;
6use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
7use Doctrine\Common\Persistence\ObjectManager;
8use Wallabag\CoreBundle\Entity\SiteCredential;
9
10class LoadSiteCredentialData extends AbstractFixture implements OrderedFixtureInterface
11{
12 /**
13 * {@inheritdoc}
14 */
15 public function load(ObjectManager $manager)
16 {
17 $credential = new SiteCredential($this->getReference('admin-user'));
18 $credential->setHost('example.com');
19 $credential->setUsername('foo');
20 $credential->setPassword('bar');
21
22 $manager->persist($credential);
23
24 $manager->flush();
25 }
26
27 /**
28 * {@inheritdoc}
29 */
30 public function getOrder()
31 {
32 return 50;
33 }
34}
diff --git a/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadTagData.php b/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadTagData.php
deleted file mode 100644
index 0ecfd18b..00000000
--- a/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadTagData.php
+++ /dev/null
@@ -1,55 +0,0 @@
1<?php
2
3namespace Wallabag\CoreBundle\DataFixtures\ORM;
4
5use Doctrine\Common\DataFixtures\AbstractFixture;
6use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
7use Doctrine\Common\Persistence\ObjectManager;
8use Wallabag\CoreBundle\Entity\Tag;
9
10class LoadTagData extends AbstractFixture implements OrderedFixtureInterface
11{
12 /**
13 * {@inheritdoc}
14 */
15 public function load(ObjectManager $manager)
16 {
17 $tag1 = new Tag();
18 $tag1->setLabel('foo bar');
19
20 $manager->persist($tag1);
21
22 $this->addReference('foo-bar-tag', $tag1);
23
24 $tag2 = new Tag();
25 $tag2->setLabel('bar');
26
27 $manager->persist($tag2);
28
29 $this->addReference('bar-tag', $tag2);
30
31 $tag3 = new Tag();
32 $tag3->setLabel('baz');
33
34 $manager->persist($tag3);
35
36 $this->addReference('baz-tag', $tag3);
37
38 $tag4 = new Tag();
39 $tag4->setLabel('foo');
40
41 $manager->persist($tag4);
42
43 $this->addReference('foo-tag', $tag4);
44
45 $manager->flush();
46 }
47
48 /**
49 * {@inheritdoc}
50 */
51 public function getOrder()
52 {
53 return 25;
54 }
55}
diff --git a/src/Wallabag/CoreBundle/DataFixtures/SiteCredentialFixtures.php b/src/Wallabag/CoreBundle/DataFixtures/SiteCredentialFixtures.php
new file mode 100644
index 00000000..9a7d116f
--- /dev/null
+++ b/src/Wallabag/CoreBundle/DataFixtures/SiteCredentialFixtures.php
@@ -0,0 +1,56 @@
1<?php
2
3namespace Wallabag\CoreBundle\DataFixtures;
4
5use Doctrine\Bundle\FixturesBundle\Fixture;
6use Doctrine\Common\DataFixtures\DependentFixtureInterface;
7use Doctrine\Common\Persistence\ObjectManager;
8use Symfony\Component\DependencyInjection\ContainerAwareInterface;
9use Symfony\Component\DependencyInjection\ContainerInterface;
10use Wallabag\CoreBundle\Entity\SiteCredential;
11use Wallabag\UserBundle\DataFixtures\UserFixtures;
12
13class SiteCredentialFixtures extends Fixture implements DependentFixtureInterface, ContainerAwareInterface
14{
15 /**
16 * @var ContainerInterface
17 */
18 private $container;
19
20 public function setContainer(ContainerInterface $container = null)
21 {
22 $this->container = $container;
23 }
24
25 /**
26 * {@inheritdoc}
27 */
28 public function load(ObjectManager $manager)
29 {
30 $credential = new SiteCredential($this->getReference('admin-user'));
31 $credential->setHost('.super.com');
32 $credential->setUsername($this->container->get('wallabag_core.helper.crypto_proxy')->crypt('.super'));
33 $credential->setPassword($this->container->get('wallabag_core.helper.crypto_proxy')->crypt('bar'));
34
35 $manager->persist($credential);
36
37 $credential = new SiteCredential($this->getReference('admin-user'));
38 $credential->setHost('paywall.example.com');
39 $credential->setUsername($this->container->get('wallabag_core.helper.crypto_proxy')->crypt('paywall.example'));
40 $credential->setPassword($this->container->get('wallabag_core.helper.crypto_proxy')->crypt('bar'));
41
42 $manager->persist($credential);
43
44 $manager->flush();
45 }
46
47 /**
48 * {@inheritdoc}
49 */
50 public function getDependencies()
51 {
52 return [
53 UserFixtures::class,
54 ];
55 }
56}
diff --git a/src/Wallabag/CoreBundle/DataFixtures/TagFixtures.php b/src/Wallabag/CoreBundle/DataFixtures/TagFixtures.php
new file mode 100644
index 00000000..58a0d799
--- /dev/null
+++ b/src/Wallabag/CoreBundle/DataFixtures/TagFixtures.php
@@ -0,0 +1,35 @@
1<?php
2
3namespace Wallabag\CoreBundle\DataFixtures;
4
5use Doctrine\Bundle\FixturesBundle\Fixture;
6use Doctrine\Common\Persistence\ObjectManager;
7use Wallabag\CoreBundle\Entity\Tag;
8
9class TagFixtures extends Fixture
10{
11 /**
12 * {@inheritdoc}
13 */
14 public function load(ObjectManager $manager)
15 {
16 $tags = [
17 'foo-bar-tag' => 'foo bar', //tag used for EntryControllerTest
18 'bar-tag' => 'bar',
19 'baz-tag' => 'baz', // tag used for ExportControllerTest
20 'foo-tag' => 'foo',
21 'bob-tag' => 'bob', // tag used for TagRestControllerTest
22 ];
23
24 foreach ($tags as $reference => $label) {
25 $tag = new Tag();
26 $tag->setLabel($label);
27
28 $manager->persist($tag);
29
30 $this->addReference($reference, $tag);
31 }
32
33 $manager->flush();
34 }
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/Configuration.php b/src/Wallabag/CoreBundle/DependencyInjection/Configuration.php
index a9791f6b..7ae73371 100644
--- a/src/Wallabag/CoreBundle/DependencyInjection/Configuration.php
+++ b/src/Wallabag/CoreBundle/DependencyInjection/Configuration.php
@@ -30,7 +30,7 @@ class Configuration implements ConfigurationInterface
30 ->defaultValue(50) 30 ->defaultValue(50)
31 ->end() 31 ->end()
32 ->integerNode('reading_speed') 32 ->integerNode('reading_speed')
33 ->defaultValue(1) 33 ->defaultValue(200)
34 ->end() 34 ->end()
35 ->scalarNode('version') 35 ->scalarNode('version')
36 ->end() 36 ->end()
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..fe7942ee 100644
--- a/src/Wallabag/CoreBundle/Entity/Config.php
+++ b/src/Wallabag/CoreBundle/Entity/Config.php
@@ -11,8 +11,12 @@ use Wallabag\UserBundle\Entity\User;
11 * Config. 11 * Config.
12 * 12 *
13 * @ORM\Entity(repositoryClass="Wallabag\CoreBundle\Repository\ConfigRepository") 13 * @ORM\Entity(repositoryClass="Wallabag\CoreBundle\Repository\ConfigRepository")
14 * @ORM\Table(name="`config`") 14 * @ORM\Table(
15 * @ORM\Entity 15 * name="`config`",
16 * indexes={
17 * @ORM\Index(name="config_feed_token", columns={"feed_token"}, options={"lengths"={255}}),
18 * }
19 * )
16 */ 20 */
17class Config 21class Config
18{ 22{
@@ -60,21 +64,21 @@ class Config
60 /** 64 /**
61 * @var string 65 * @var string
62 * 66 *
63 * @ORM\Column(name="rss_token", type="string", nullable=true) 67 * @ORM\Column(name="feed_token", type="string", nullable=true)
64 */ 68 */
65 private $rssToken; 69 private $feedToken;
66 70
67 /** 71 /**
68 * @var int 72 * @var int
69 * 73 *
70 * @ORM\Column(name="rss_limit", type="integer", nullable=true) 74 * @ORM\Column(name="feed_limit", type="integer", nullable=true)
71 * @Assert\Range( 75 * @Assert\Range(
72 * min = 1, 76 * min = 1,
73 * max = 100000, 77 * max = 100000,
74 * maxMessage = "validator.rss_limit_too_high" 78 * maxMessage = "validator.feed_limit_too_high"
75 * ) 79 * )
76 */ 80 */
77 private $rssLimit; 81 private $feedLimit;
78 82
79 /** 83 /**
80 * @var float 84 * @var float
@@ -231,51 +235,51 @@ class Config
231 } 235 }
232 236
233 /** 237 /**
234 * Set rssToken. 238 * Set feed Token.
235 * 239 *
236 * @param string $rssToken 240 * @param string $feedToken
237 * 241 *
238 * @return Config 242 * @return Config
239 */ 243 */
240 public function setRssToken($rssToken) 244 public function setFeedToken($feedToken)
241 { 245 {
242 $this->rssToken = $rssToken; 246 $this->feedToken = $feedToken;
243 247
244 return $this; 248 return $this;
245 } 249 }
246 250
247 /** 251 /**
248 * Get rssToken. 252 * Get feedToken.
249 * 253 *
250 * @return string 254 * @return string
251 */ 255 */
252 public function getRssToken() 256 public function getFeedToken()
253 { 257 {
254 return $this->rssToken; 258 return $this->feedToken;
255 } 259 }
256 260
257 /** 261 /**
258 * Set rssLimit. 262 * Set Feed Limit.
259 * 263 *
260 * @param int $rssLimit 264 * @param int $feedLimit
261 * 265 *
262 * @return Config 266 * @return Config
263 */ 267 */
264 public function setRssLimit($rssLimit) 268 public function setFeedLimit($feedLimit)
265 { 269 {
266 $this->rssLimit = $rssLimit; 270 $this->feedLimit = $feedLimit;
267 271
268 return $this; 272 return $this;
269 } 273 }
270 274
271 /** 275 /**
272 * Get rssLimit. 276 * Get Feed Limit.
273 * 277 *
274 * @return int 278 * @return int
275 */ 279 */
276 public function getRssLimit() 280 public function getFeedLimit()
277 { 281 {
278 return $this->rssLimit; 282 return $this->feedLimit;
279 } 283 }
280 284
281 /** 285 /**
@@ -367,8 +371,6 @@ class Config
367 } 371 }
368 372
369 /** 373 /**
370 * @param TaggingRule $rule
371 *
372 * @return Config 374 * @return Config
373 */ 375 */
374 public function addTaggingRule(TaggingRule $rule) 376 public function addTaggingRule(TaggingRule $rule)
diff --git a/src/Wallabag/CoreBundle/Entity/Entry.php b/src/Wallabag/CoreBundle/Entity/Entry.php
index 2b1f2e05..19045798 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,13 @@ 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}}),
31 * @ORM\Index(name="hashed_given_url_user_id", columns={"user_id", "hashed_given_url"}, options={"lengths"={null, 40}}),
32 * @ORM\Index(name="user_language", columns={"language", "user_id"}),
33 * @ORM\Index(name="user_archived", columns={"user_id", "is_archived", "archived_at"}),
34 * @ORM\Index(name="user_created", columns={"user_id", "created_at"}),
35 * @ORM\Index(name="user_starred", columns={"user_id", "is_starred", "starred_at"})
29 * } 36 * }
30 * ) 37 * )
31 * @ORM\HasLifecycleCallbacks() 38 * @ORM\HasLifecycleCallbacks()
@@ -66,6 +73,8 @@ class Entry
66 private $title; 73 private $title;
67 74
68 /** 75 /**
76 * Define the url fetched by wallabag (the final url after potential redirections).
77 *
69 * @var string 78 * @var string
70 * 79 *
71 * @Assert\NotBlank() 80 * @Assert\NotBlank()
@@ -76,6 +85,42 @@ class Entry
76 private $url; 85 private $url;
77 86
78 /** 87 /**
88 * @var string
89 *
90 * @ORM\Column(name="hashed_url", type="string", length=40, nullable=true)
91 */
92 private $hashedUrl;
93
94 /**
95 * From where user retrieved/found the url (an other article, a twitter, or the given_url if non are provided).
96 *
97 * @var string
98 *
99 * @ORM\Column(name="origin_url", type="text", nullable=true)
100 *
101 * @Groups({"entries_for_user", "export_all"})
102 */
103 private $originUrl;
104
105 /**
106 * Define the url entered by the user (without redirections).
107 *
108 * @var string
109 *
110 * @ORM\Column(name="given_url", type="text", nullable=true)
111 *
112 * @Groups({"entries_for_user", "export_all"})
113 */
114 private $givenUrl;
115
116 /**
117 * @var string
118 *
119 * @ORM\Column(name="hashed_given_url", type="string", length=40, nullable=true)
120 */
121 private $hashedGivenUrl;
122
123 /**
79 * @var bool 124 * @var bool
80 * 125 *
81 * @Exclude 126 * @Exclude
@@ -87,6 +132,15 @@ class Entry
87 private $isArchived = false; 132 private $isArchived = false;
88 133
89 /** 134 /**
135 * @var \DateTime
136 *
137 * @ORM\Column(name="archived_at", type="datetime", nullable=true)
138 *
139 * @Groups({"entries_for_user", "export_all"})
140 */
141 private $archivedAt = null;
142
143 /**
90 * @var bool 144 * @var bool
91 * 145 *
92 * @Exclude 146 * @Exclude
@@ -171,7 +225,7 @@ class Entry
171 /** 225 /**
172 * @var string 226 * @var string
173 * 227 *
174 * @ORM\Column(name="language", type="text", nullable=true) 228 * @ORM\Column(name="language", type="string", length=20, nullable=true)
175 * 229 *
176 * @Groups({"entries_for_user", "export_all"}) 230 * @Groups({"entries_for_user", "export_all"})
177 */ 231 */
@@ -245,15 +299,6 @@ class Entry
245 */ 299 */
246 private $tags; 300 private $tags;
247 301
248 /**
249 * @var string
250 *
251 * @ORM\Column(name="origin_url", type="text", nullable=true)
252 *
253 * @Groups({"entries_for_user", "export_all"})
254 */
255 private $originUrl;
256
257 /* 302 /*
258 * @param User $user 303 * @param User $user
259 */ 304 */
@@ -307,6 +352,7 @@ class Entry
307 public function setUrl($url) 352 public function setUrl($url)
308 { 353 {
309 $this->url = $url; 354 $this->url = $url;
355 $this->hashedUrl = UrlHasher::hashUrl($url);
310 356
311 return $this; 357 return $this;
312 } 358 }
@@ -336,6 +382,44 @@ class Entry
336 } 382 }
337 383
338 /** 384 /**
385 * update isArchived and archive_at fields.
386 *
387 * @param bool $isArchived
388 *
389 * @return Entry
390 */
391 public function updateArchived($isArchived = false)
392 {
393 $this->setArchived($isArchived);
394 $this->setArchivedAt(null);
395 if ($this->isArchived()) {
396 $this->setArchivedAt(new \DateTime());
397 }
398
399 return $this;
400 }
401
402 /**
403 * @return \DateTime|null
404 */
405 public function getArchivedAt()
406 {
407 return $this->archivedAt;
408 }
409
410 /**
411 * @param \DateTime|null $archivedAt
412 *
413 * @return Entry
414 */
415 public function setArchivedAt($archivedAt = null)
416 {
417 $this->archivedAt = $archivedAt;
418
419 return $this;
420 }
421
422 /**
339 * Get isArchived. 423 * Get isArchived.
340 * 424 *
341 * @return bool 425 * @return bool
@@ -357,7 +441,7 @@ class Entry
357 441
358 public function toggleArchive() 442 public function toggleArchive()
359 { 443 {
360 $this->isArchived = $this->isArchived() ^ 1; 444 $this->updateArchived($this->isArchived() ^ 1);
361 445
362 return $this; 446 return $this;
363 } 447 }
@@ -466,8 +550,6 @@ class Entry
466 * Set created_at. 550 * Set created_at.
467 * Only used when importing data from an other service. 551 * Only used when importing data from an other service.
468 * 552 *
469 * @param \DateTime $createdAt
470 *
471 * @return Entry 553 * @return Entry
472 */ 554 */
473 public function setCreatedAt(\DateTime $createdAt) 555 public function setCreatedAt(\DateTime $createdAt)
@@ -539,9 +621,6 @@ class Entry
539 return $this->annotations; 621 return $this->annotations;
540 } 622 }
541 623
542 /**
543 * @param Annotation $annotation
544 */
545 public function setAnnotation(Annotation $annotation) 624 public function setAnnotation(Annotation $annotation)
546 { 625 {
547 $this->annotations[] = $annotation; 626 $this->annotations[] = $annotation;
@@ -618,9 +697,6 @@ class Entry
618 return $data; 697 return $data;
619 } 698 }
620 699
621 /**
622 * @param Tag $tag
623 */
624 public function addTag(Tag $tag) 700 public function addTag(Tag $tag)
625 { 701 {
626 if ($this->tags->contains($tag)) { 702 if ($this->tags->contains($tag)) {
@@ -641,8 +717,6 @@ class Entry
641 717
642 /** 718 /**
643 * Remove the given tag from the entry (if the tag is associated). 719 * Remove the given tag from the entry (if the tag is associated).
644 *
645 * @param Tag $tag
646 */ 720 */
647 public function removeTag(Tag $tag) 721 public function removeTag(Tag $tag)
648 { 722 {
@@ -714,7 +788,20 @@ class Entry
714 } 788 }
715 789
716 /** 790 /**
717 * @return string 791 * Format the entry language to a valid html lang attribute.
792 */
793 public function getHTMLLanguage()
794 {
795 $parsedLocale = \Locale::parseLocale($this->getLanguage());
796 $lang = '';
797 $lang .= $parsedLocale['language'] ?? '';
798 $lang .= isset($parsedLocale['region']) ? '-' . $parsedLocale['region'] : '';
799
800 return $lang;
801 }
802
803 /**
804 * @return string|null
718 */ 805 */
719 public function getUid() 806 public function getUid()
720 { 807 {
@@ -790,8 +877,6 @@ class Entry
790 } 877 }
791 878
792 /** 879 /**
793 * @param \Datetime $publishedAt
794 *
795 * @return Entry 880 * @return Entry
796 */ 881 */
797 public function setPublishedAt(\Datetime $publishedAt) 882 public function setPublishedAt(\Datetime $publishedAt)
@@ -864,4 +949,49 @@ class Entry
864 { 949 {
865 return $this->originUrl; 950 return $this->originUrl;
866 } 951 }
952
953 /**
954 * Set given url.
955 *
956 * @param string $givenUrl
957 *
958 * @return Entry
959 */
960 public function setGivenUrl($givenUrl)
961 {
962 $this->givenUrl = $givenUrl;
963 $this->hashedGivenUrl = UrlHasher::hashUrl($givenUrl);
964
965 return $this;
966 }
967
968 /**
969 * Get given url.
970 *
971 * @return string
972 */
973 public function getGivenUrl()
974 {
975 return $this->givenUrl;
976 }
977
978 /**
979 * @return string
980 */
981 public function getHashedUrl()
982 {
983 return $this->hashedUrl;
984 }
985
986 /**
987 * @param mixed $hashedUrl
988 *
989 * @return Entry
990 */
991 public function setHashedUrl($hashedUrl)
992 {
993 $this->hashedUrl = $hashedUrl;
994
995 return $this;
996 }
867} 997}
diff --git a/src/Wallabag/CoreBundle/Entity/InternalSetting.php b/src/Wallabag/CoreBundle/Entity/InternalSetting.php
new file mode 100644
index 00000000..df8bd3be
--- /dev/null
+++ b/src/Wallabag/CoreBundle/Entity/InternalSetting.php
@@ -0,0 +1,36 @@
1<?php
2
3namespace Wallabag\CoreBundle\Entity;
4
5use Craue\ConfigBundle\Entity\BaseSetting;
6use Doctrine\ORM\Mapping as ORM;
7
8/**
9 * InternalSetting.
10 *
11 * Re-define setting so we can override length attribute to fix utf8mb4 issue.
12 *
13 * @ORM\Entity(repositoryClass="Craue\ConfigBundle\Repository\SettingRepository")
14 * @ORM\Table(name="`internal_setting`")
15 * @ORM\AttributeOverrides({
16 * @ORM\AttributeOverride(name="name",
17 * column=@ORM\Column(
18 * length = 191
19 * )
20 * ),
21 * @ORM\AttributeOverride(name="section",
22 * column=@ORM\Column(
23 * length = 191
24 * )
25 * )
26 * })
27 */
28class InternalSetting extends BaseSetting
29{
30 /**
31 * @var string|null
32 *
33 * @ORM\Column(name="value", type="string", nullable=true, length=191)
34 */
35 protected $value;
36}
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/Tag.php b/src/Wallabag/CoreBundle/Entity/Tag.php
index a6dc8c50..9fb2f94f 100644
--- a/src/Wallabag/CoreBundle/Entity/Tag.php
+++ b/src/Wallabag/CoreBundle/Entity/Tag.php
@@ -13,7 +13,13 @@ use JMS\Serializer\Annotation\XmlRoot;
13 * Tag. 13 * Tag.
14 * 14 *
15 * @XmlRoot("tag") 15 * @XmlRoot("tag")
16 * @ORM\Table(name="`tag`") 16 * @ORM\Table(
17 * name="`tag`",
18 * options={"collate"="utf8mb4_bin", "charset"="utf8mb4"},
19 * indexes={
20 * @ORM\Index(name="tag_label", columns={"label"}, options={"lengths"={255}}),
21 * }
22 * )
17 * @ORM\Entity(repositoryClass="Wallabag\CoreBundle\Repository\TagRepository") 23 * @ORM\Entity(repositoryClass="Wallabag\CoreBundle\Repository\TagRepository")
18 * @ExclusionPolicy("all") 24 * @ExclusionPolicy("all")
19 */ 25 */
@@ -98,9 +104,6 @@ class Tag
98 return $this->slug; 104 return $this->slug;
99 } 105 }
100 106
101 /**
102 * @param Entry $entry
103 */
104 public function addEntry(Entry $entry) 107 public function addEntry(Entry $entry)
105 { 108 {
106 if ($this->entries->contains($entry)) { 109 if ($this->entries->contains($entry)) {
@@ -111,9 +114,6 @@ class Tag
111 $entry->addTag($this); 114 $entry->addTag($this);
112 } 115 }
113 116
114 /**
115 * @param Entry $entry
116 */
117 public function removeEntry(Entry $entry) 117 public function removeEntry(Entry $entry)
118 { 118 {
119 if (!$this->entries->contains($entry)) { 119 if (!$this->entries->contains($entry)) {
diff --git a/src/Wallabag/CoreBundle/Entity/TaggingRule.php b/src/Wallabag/CoreBundle/Entity/TaggingRule.php
index 84e11e26..f7166087 100644
--- a/src/Wallabag/CoreBundle/Entity/TaggingRule.php
+++ b/src/Wallabag/CoreBundle/Entity/TaggingRule.php
@@ -3,12 +3,16 @@
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 JMS\Serializer\Annotation\Exclude;
7use JMS\Serializer\Annotation\Groups;
8use JMS\Serializer\Annotation\XmlRoot;
9use Symfony\Bridge\RulerZ\Validator\Constraints as RulerZAssert;
7use Symfony\Component\Validator\Constraints as Assert; 10use Symfony\Component\Validator\Constraints as Assert;
8 11
9/** 12/**
10 * Tagging rule. 13 * Tagging rule.
11 * 14 *
15 * @XmlRoot("tagging_rule")
12 * @ORM\Entity(repositoryClass="Wallabag\CoreBundle\Repository\TaggingRuleRepository") 16 * @ORM\Entity(repositoryClass="Wallabag\CoreBundle\Repository\TaggingRuleRepository")
13 * @ORM\Table(name="`tagging_rule`") 17 * @ORM\Table(name="`tagging_rule`")
14 * @ORM\Entity 18 * @ORM\Entity
@@ -34,6 +38,8 @@ class TaggingRule
34 * allowed_operators={">", "<", ">=", "<=", "=", "is", "!=", "and", "not", "or", "matches", "notmatches"} 38 * allowed_operators={">", "<", ">=", "<=", "=", "is", "!=", "and", "not", "or", "matches", "notmatches"}
35 * ) 39 * )
36 * @ORM\Column(name="rule", type="string", nullable=false) 40 * @ORM\Column(name="rule", type="string", nullable=false)
41 *
42 * @Groups({"export_tagging_rule"})
37 */ 43 */
38 private $rule; 44 private $rule;
39 45
@@ -42,10 +48,14 @@ class TaggingRule
42 * 48 *
43 * @Assert\NotBlank() 49 * @Assert\NotBlank()
44 * @ORM\Column(name="tags", type="simple_array", nullable=false) 50 * @ORM\Column(name="tags", type="simple_array", nullable=false)
51 *
52 * @Groups({"export_tagging_rule"})
45 */ 53 */
46 private $tags = []; 54 private $tags = [];
47 55
48 /** 56 /**
57 * @Exclude
58 *
49 * @ORM\ManyToOne(targetEntity="Wallabag\CoreBundle\Entity\Config", inversedBy="taggingRules") 59 * @ORM\ManyToOne(targetEntity="Wallabag\CoreBundle\Entity\Config", inversedBy="taggingRules")
50 */ 60 */
51 private $config; 61 private $config;
@@ -111,8 +121,6 @@ class TaggingRule
111 /** 121 /**
112 * Set config. 122 * Set config.
113 * 123 *
114 * @param Config $config
115 *
116 * @return TaggingRule 124 * @return TaggingRule
117 */ 125 */
118 public function setConfig(Config $config) 126 public function setConfig(Config $config)
diff --git a/src/Wallabag/CoreBundle/Event/Listener/UserLocaleListener.php b/src/Wallabag/CoreBundle/Event/Listener/UserLocaleListener.php
index 367cdfb0..1b5d61ad 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 */
@@ -23,14 +25,11 @@ class UserLocaleListener
23 $this->session = $session; 25 $this->session = $session;
24 } 26 }
25 27
26 /**
27 * @param InteractiveLoginEvent $event
28 */
29 public function onInteractiveLogin(InteractiveLoginEvent $event) 28 public function onInteractiveLogin(InteractiveLoginEvent $event)
30 { 29 {
31 $user = $event->getAuthenticationToken()->getUser(); 30 $user = $event->getAuthenticationToken()->getUser();
32 31
33 if (null !== $user->getConfig()->getLanguage()) { 32 if (null !== $user->getConfig()->getLanguage() && null === $this->session->get('_locale')) {
34 $this->session->set('_locale', $user->getConfig()->getLanguage()); 33 $this->session->set('_locale', $user->getConfig()->getLanguage());
35 } 34 }
36 } 35 }
diff --git a/src/Wallabag/CoreBundle/Event/Subscriber/CustomDoctrineORMSubscriber.php b/src/Wallabag/CoreBundle/Event/Subscriber/CustomDoctrineORMSubscriber.php
index cabb3eca..b8f6e1d6 100644
--- a/src/Wallabag/CoreBundle/Event/Subscriber/CustomDoctrineORMSubscriber.php
+++ b/src/Wallabag/CoreBundle/Event/Subscriber/CustomDoctrineORMSubscriber.php
@@ -12,9 +12,6 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface;
12 */ 12 */
13class CustomDoctrineORMSubscriber extends DoctrineORMSubscriber implements EventSubscriberInterface 13class CustomDoctrineORMSubscriber extends DoctrineORMSubscriber implements EventSubscriberInterface
14{ 14{
15 /**
16 * @param GetFilterConditionEvent $event
17 */
18 public function filterDateRange(GetFilterConditionEvent $event) 15 public function filterDateRange(GetFilterConditionEvent $event)
19 { 16 {
20 $expr = $event->getFilterQuery()->getExpressionBuilder(); 17 $expr = $event->getFilterQuery()->getExpressionBuilder();
diff --git a/src/Wallabag/CoreBundle/Event/Subscriber/DownloadImagesSubscriber.php b/src/Wallabag/CoreBundle/Event/Subscriber/DownloadImagesSubscriber.php
index 1dd0a1a4..ef8d7d3b 100644
--- a/src/Wallabag/CoreBundle/Event/Subscriber/DownloadImagesSubscriber.php
+++ b/src/Wallabag/CoreBundle/Event/Subscriber/DownloadImagesSubscriber.php
@@ -35,8 +35,6 @@ class DownloadImagesSubscriber implements EventSubscriberInterface
35 35
36 /** 36 /**
37 * Download images and updated the data into the entry. 37 * Download images and updated the data into the entry.
38 *
39 * @param EntrySavedEvent $event
40 */ 38 */
41 public function onEntrySaved(EntrySavedEvent $event) 39 public function onEntrySaved(EntrySavedEvent $event)
42 { 40 {
@@ -69,8 +67,6 @@ class DownloadImagesSubscriber implements EventSubscriberInterface
69 67
70 /** 68 /**
71 * Remove images related to the entry. 69 * Remove images related to the entry.
72 *
73 * @param EntryDeletedEvent $event
74 */ 70 */
75 public function onEntryDeleted(EntryDeletedEvent $event) 71 public function onEntryDeleted(EntryDeletedEvent $event)
76 { 72 {
@@ -88,8 +84,6 @@ class DownloadImagesSubscriber implements EventSubscriberInterface
88 * 84 *
89 * @todo If we want to add async download, it should be done in that method 85 * @todo If we want to add async download, it should be done in that method
90 * 86 *
91 * @param Entry $entry
92 *
93 * @return string|false False in case of async 87 * @return string|false False in case of async
94 */ 88 */
95 private function downloadImages(Entry $entry) 89 private function downloadImages(Entry $entry)
@@ -106,8 +100,6 @@ class DownloadImagesSubscriber implements EventSubscriberInterface
106 * 100 *
107 * @todo If we want to add async download, it should be done in that method 101 * @todo If we want to add async download, it should be done in that method
108 * 102 *
109 * @param Entry $entry
110 *
111 * @return string|false False in case of async 103 * @return string|false False in case of async
112 */ 104 */
113 private function downloadPreviewImage(Entry $entry) 105 private function downloadPreviewImage(Entry $entry)
diff --git a/src/Wallabag/CoreBundle/Event/Subscriber/SQLiteCascadeDeleteSubscriber.php b/src/Wallabag/CoreBundle/Event/Subscriber/SQLiteCascadeDeleteSubscriber.php
index 9c1d8a1d..dcadeedf 100644
--- a/src/Wallabag/CoreBundle/Event/Subscriber/SQLiteCascadeDeleteSubscriber.php
+++ b/src/Wallabag/CoreBundle/Event/Subscriber/SQLiteCascadeDeleteSubscriber.php
@@ -18,9 +18,6 @@ class SQLiteCascadeDeleteSubscriber implements EventSubscriber
18{ 18{
19 private $doctrine; 19 private $doctrine;
20 20
21 /**
22 * @param \Doctrine\Bundle\DoctrineBundle\Registry $doctrine
23 */
24 public function __construct(Registry $doctrine) 21 public function __construct(Registry $doctrine)
25 { 22 {
26 $this->doctrine = $doctrine; 23 $this->doctrine = $doctrine;
@@ -39,8 +36,6 @@ class SQLiteCascadeDeleteSubscriber implements EventSubscriber
39 /** 36 /**
40 * We removed everything related to the upcoming removed entry because SQLite can't handle it on it own. 37 * We removed everything related to the upcoming removed entry because SQLite can't handle it on it own.
41 * We do it in the preRemove, because we can't retrieve tags in the postRemove (because the entry id is gone). 38 * We do it in the preRemove, because we can't retrieve tags in the postRemove (because the entry id is gone).
42 *
43 * @param LifecycleEventArgs $args
44 */ 39 */
45 public function preRemove(LifecycleEventArgs $args) 40 public function preRemove(LifecycleEventArgs $args)
46 { 41 {
diff --git a/src/Wallabag/CoreBundle/Form/Type/ConfigType.php b/src/Wallabag/CoreBundle/Form/Type/ConfigType.php
index 1714ce74..6901fa08 100644
--- a/src/Wallabag/CoreBundle/Form/Type/ConfigType.php
+++ b/src/Wallabag/CoreBundle/Form/Type/ConfigType.php
@@ -4,6 +4,7 @@ namespace Wallabag\CoreBundle\Form\Type;
4 4
5use Symfony\Component\Form\AbstractType; 5use Symfony\Component\Form\AbstractType;
6use Symfony\Component\Form\Extension\Core\Type\ChoiceType; 6use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
7use Symfony\Component\Form\Extension\Core\Type\IntegerType;
7use Symfony\Component\Form\Extension\Core\Type\SubmitType; 8use Symfony\Component\Form\Extension\Core\Type\SubmitType;
8use Symfony\Component\Form\FormBuilderInterface; 9use Symfony\Component\Form\FormBuilderInterface;
9use Symfony\Component\OptionsResolver\OptionsResolver; 10use Symfony\Component\OptionsResolver\OptionsResolver;
@@ -37,19 +38,13 @@ class ConfigType extends AbstractType
37 'choices' => array_flip($this->themes), 38 'choices' => array_flip($this->themes),
38 'label' => 'config.form_settings.theme_label', 39 'label' => 'config.form_settings.theme_label',
39 ]) 40 ])
40 ->add('items_per_page', null, [ 41 ->add('items_per_page', IntegerType::class, [
41 'label' => 'config.form_settings.items_per_page_label', 42 'label' => 'config.form_settings.items_per_page_label',
42 'property_path' => 'itemsPerPage', 43 'property_path' => 'itemsPerPage',
43 ]) 44 ])
44 ->add('reading_speed', ChoiceType::class, [ 45 ->add('reading_speed', IntegerType::class, [
45 'label' => 'config.form_settings.reading_speed.label', 46 'label' => 'config.form_settings.reading_speed.label',
46 'property_path' => 'readingSpeed', 47 'property_path' => 'readingSpeed',
47 'choices' => [
48 'config.form_settings.reading_speed.100_word' => '0.5',
49 'config.form_settings.reading_speed.200_word' => '1',
50 'config.form_settings.reading_speed.300_word' => '1.5',
51 'config.form_settings.reading_speed.400_word' => '2',
52 ],
53 ]) 48 ])
54 ->add('action_mark_as_read', ChoiceType::class, [ 49 ->add('action_mark_as_read', ChoiceType::class, [
55 'label' => 'config.form_settings.action_mark_as_read.label', 50 'label' => 'config.form_settings.action_mark_as_read.label',
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/EntryFilterType.php b/src/Wallabag/CoreBundle/Form/Type/EntryFilterType.php
index 702c7f7a..17070c59 100644
--- a/src/Wallabag/CoreBundle/Form/Type/EntryFilterType.php
+++ b/src/Wallabag/CoreBundle/Form/Type/EntryFilterType.php
@@ -23,9 +23,6 @@ class EntryFilterType extends AbstractType
23 23
24 /** 24 /**
25 * Repository & user are used to get a list of language entries for this user. 25 * Repository & user are used to get a list of language entries for this user.
26 *
27 * @param EntityRepository $entryRepository
28 * @param TokenStorageInterface $tokenStorage
29 */ 26 */
30 public function __construct(EntityRepository $entryRepository, TokenStorageInterface $tokenStorage) 27 public function __construct(EntityRepository $entryRepository, TokenStorageInterface $tokenStorage)
31 { 28 {
@@ -54,8 +51,8 @@ class EntryFilterType extends AbstractType
54 $lower = $values['value']['left_number'][0]; 51 $lower = $values['value']['left_number'][0];
55 $upper = $values['value']['right_number'][0]; 52 $upper = $values['value']['right_number'][0];
56 53
57 $min = (int) ($lower * $this->user->getConfig()->getReadingSpeed()); 54 $min = (int) ($lower * $this->user->getConfig()->getReadingSpeed() / 200);
58 $max = (int) ($upper * $this->user->getConfig()->getReadingSpeed()); 55 $max = (int) ($upper * $this->user->getConfig()->getReadingSpeed() / 200);
59 56
60 if (null === $lower && null === $upper) { 57 if (null === $lower && null === $upper) {
61 // no value? no filter 58 // no value? no filter
@@ -108,7 +105,7 @@ class EntryFilterType extends AbstractType
108 ->add('httpStatus', TextFilterType::class, [ 105 ->add('httpStatus', TextFilterType::class, [
109 'apply_filter' => function (QueryInterface $filterQuery, $field, $values) { 106 'apply_filter' => function (QueryInterface $filterQuery, $field, $values) {
110 $value = $values['value']; 107 $value = $values['value'];
111 if (false === array_key_exists($value, Response::$statusTexts)) { 108 if (false === \array_key_exists($value, Response::$statusTexts)) {
112 return; 109 return;
113 } 110 }
114 111
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/TaggingRuleImportType.php b/src/Wallabag/CoreBundle/Form/Type/TaggingRuleImportType.php
new file mode 100644
index 00000000..c6a8c0b8
--- /dev/null
+++ b/src/Wallabag/CoreBundle/Form/Type/TaggingRuleImportType.php
@@ -0,0 +1,29 @@
1<?php
2
3namespace Wallabag\CoreBundle\Form\Type;
4
5use Symfony\Component\Form\AbstractType;
6use Symfony\Component\Form\Extension\Core\Type\FileType;
7use Symfony\Component\Form\Extension\Core\Type\SubmitType;
8use Symfony\Component\Form\FormBuilderInterface;
9
10class TaggingRuleImportType extends AbstractType
11{
12 public function buildForm(FormBuilderInterface $builder, array $options)
13 {
14 $builder
15 ->add('file', FileType::class, [
16 'label' => 'config.form_rules.file_label',
17 'required' => true,
18 ])
19 ->add('import', SubmitType::class, [
20 'label' => 'config.form_rules.import_submit',
21 ])
22 ;
23 }
24
25 public function getBlockPrefix()
26 {
27 return 'upload_tagging_rule_file';
28 }
29}
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/GuzzleSiteAuthenticator/GrabySiteConfigBuilder.php b/src/Wallabag/CoreBundle/GuzzleSiteAuthenticator/GrabySiteConfigBuilder.php
index 90e00c62..b0be2176 100644
--- a/src/Wallabag/CoreBundle/GuzzleSiteAuthenticator/GrabySiteConfigBuilder.php
+++ b/src/Wallabag/CoreBundle/GuzzleSiteAuthenticator/GrabySiteConfigBuilder.php
@@ -34,11 +34,6 @@ class GrabySiteConfigBuilder implements SiteConfigBuilder
34 34
35 /** 35 /**
36 * GrabySiteConfigBuilder constructor. 36 * GrabySiteConfigBuilder constructor.
37 *
38 * @param ConfigBuilder $grabyConfigBuilder
39 * @param TokenStorage $token
40 * @param SiteCredentialRepository $credentialRepository
41 * @param LoggerInterface $logger
42 */ 37 */
43 public function __construct(ConfigBuilder $grabyConfigBuilder, TokenStorage $token, SiteCredentialRepository $credentialRepository, LoggerInterface $logger) 38 public function __construct(ConfigBuilder $grabyConfigBuilder, TokenStorage $token, SiteCredentialRepository $credentialRepository, LoggerInterface $logger)
44 { 39 {
@@ -62,11 +57,24 @@ class GrabySiteConfigBuilder implements SiteConfigBuilder
62 $host = substr($host, 4); 57 $host = substr($host, 4);
63 } 58 }
64 59
65 $credentials = null; 60 if (!$this->currentUser) {
66 if ($this->currentUser) { 61 $this->logger->debug('Auth: no current user defined.');
67 $credentials = $this->credentialRepository->findOneByHostAndUser($host, $this->currentUser->getId()); 62
63 return false;
68 } 64 }
69 65
66 $hosts = [$host];
67 // will try to see for a host without the first subdomain (fr.example.org & .example.org)
68 $split = explode('.', $host);
69
70 if (\count($split) > 1) {
71 // remove first subdomain
72 array_shift($split);
73 $hosts[] = '.' . implode('.', $split);
74 }
75
76 $credentials = $this->credentialRepository->findOneByHostsAndUser($hosts, $this->currentUser->getId());
77
70 if (null === $credentials) { 78 if (null === $credentials) {
71 $this->logger->debug('Auth: no credentials available for host.', ['host' => $host]); 79 $this->logger->debug('Auth: no credentials available for host.', ['host' => $host]);
72 80
diff --git a/src/Wallabag/CoreBundle/Helper/ContentProxy.php b/src/Wallabag/CoreBundle/Helper/ContentProxy.php
index d38811a2..9c6fa8db 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{
@@ -47,13 +47,18 @@ class ContentProxy
47 */ 47 */
48 public function updateEntry(Entry $entry, $url, array $content = [], $disableContentUpdate = false) 48 public function updateEntry(Entry $entry, $url, array $content = [], $disableContentUpdate = false)
49 { 49 {
50 $this->graby->toggleImgNoReferrer(true);
50 if (!empty($content['html'])) { 51 if (!empty($content['html'])) {
51 $content['html'] = $this->graby->cleanupHtml($content['html'], $url); 52 $content['html'] = $this->graby->cleanupHtml($content['html'], $url);
52 } 53 }
53 54
54 if ((empty($content) || false === $this->validateContent($content)) && false === $disableContentUpdate) { 55 if ((empty($content) || false === $this->validateContent($content)) && false === $disableContentUpdate) {
55 $fetchedContent = $this->graby->fetchContent($url); 56 $fetchedContent = $this->graby->fetchContent($url);
56 $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 );
57 62
58 // when content is imported, we have information in $content 63 // when content is imported, we have information in $content
59 // 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
@@ -73,13 +78,14 @@ class ContentProxy
73 $entry->setUrl($url); 78 $entry->setUrl($url);
74 } 79 }
75 80
81 $entry->setGivenUrl($url);
82
76 $this->stockEntry($entry, $content); 83 $this->stockEntry($entry, $content);
77 } 84 }
78 85
79 /** 86 /**
80 * Use a Symfony validator to ensure the language is well formatted. 87 * Use a Symfony validator to ensure the language is well formatted.
81 * 88 *
82 * @param Entry $entry
83 * @param string $value Language to validate and save 89 * @param string $value Language to validate and save
84 */ 90 */
85 public function updateLanguage(Entry $entry, $value) 91 public function updateLanguage(Entry $entry, $value)
@@ -105,7 +111,6 @@ class ContentProxy
105 /** 111 /**
106 * Use a Symfony validator to ensure the preview picture is a real url. 112 * Use a Symfony validator to ensure the preview picture is a real url.
107 * 113 *
108 * @param Entry $entry
109 * @param string $value URL to validate and save 114 * @param string $value URL to validate and save
110 */ 115 */
111 public function updatePreviewPicture(Entry $entry, $value) 116 public function updatePreviewPicture(Entry $entry, $value)
@@ -127,7 +132,6 @@ class ContentProxy
127 /** 132 /**
128 * Update date. 133 * Update date.
129 * 134 *
130 * @param Entry $entry
131 * @param string $value Date to validate and save 135 * @param string $value Date to validate and save
132 */ 136 */
133 public function updatePublishedAt(Entry $entry, $value) 137 public function updatePublishedAt(Entry $entry, $value)
@@ -154,8 +158,6 @@ class ContentProxy
154 158
155 /** 159 /**
156 * Helper to extract and save host from entry url. 160 * Helper to extract and save host from entry url.
157 *
158 * @param Entry $entry
159 */ 161 */
160 public function setEntryDomainName(Entry $entry) 162 public function setEntryDomainName(Entry $entry)
161 { 163 {
@@ -169,8 +171,6 @@ class ContentProxy
169 * Helper to set a default title using: 171 * Helper to set a default title using:
170 * - url basename, if applicable 172 * - url basename, if applicable
171 * - hostname. 173 * - hostname.
172 *
173 * @param Entry $entry
174 */ 174 */
175 public function setDefaultEntryTitle(Entry $entry) 175 public function setDefaultEntryTitle(Entry $entry)
176 { 176 {
@@ -187,8 +187,8 @@ class ContentProxy
187 /** 187 /**
188 * Try to sanitize the title of the fetched content from wrong character encodings and invalid UTF-8 character. 188 * Try to sanitize the title of the fetched content from wrong character encodings and invalid UTF-8 character.
189 * 189 *
190 * @param $title 190 * @param string $title
191 * @param $contentType 191 * @param string $contentType
192 * 192 *
193 * @return string 193 * @return string
194 */ 194 */
@@ -252,22 +252,19 @@ class ContentProxy
252 252
253 if (!empty($content['title'])) { 253 if (!empty($content['title'])) {
254 $entry->setTitle($content['title']); 254 $entry->setTitle($content['title']);
255 } elseif (!empty($content['open_graph']['og_title'])) {
256 $entry->setTitle($content['open_graph']['og_title']);
257 } 255 }
258 256
259 $html = $content['html']; 257 if (empty($content['html'])) {
260 if (false === $html) { 258 $content['html'] = $this->fetchingErrorMessage;
261 $html = $this->fetchingErrorMessage;
262 259
263 if (!empty($content['open_graph']['og_description'])) { 260 if (!empty($content['description'])) {
264 $html .= '<p><i>But we found a short description: </i></p>'; 261 $content['html'] .= '<p><i>But we found a short description: </i></p>';
265 $html .= $content['open_graph']['og_description']; 262 $content['html'] .= $content['description'];
266 } 263 }
267 } 264 }
268 265
269 $entry->setContent($html); 266 $entry->setContent($content['html']);
270 $entry->setReadingTime(Utils::getReadingTime($html)); 267 $entry->setReadingTime(Utils::getReadingTime($content['html']));
271 268
272 if (!empty($content['status'])) { 269 if (!empty($content['status'])) {
273 $entry->setHttpStatus($content['status']); 270 $entry->setHttpStatus($content['status']);
@@ -277,8 +274,8 @@ class ContentProxy
277 $entry->setPublishedBy($content['authors']); 274 $entry->setPublishedBy($content['authors']);
278 } 275 }
279 276
280 if (!empty($content['all_headers']) && $this->storeArticleHeaders) { 277 if (!empty($content['headers'])) {
281 $entry->setHeaders($content['all_headers']); 278 $entry->setHeaders($content['headers']);
282 } 279 }
283 280
284 if (!empty($content['date'])) { 281 if (!empty($content['date'])) {
@@ -289,17 +286,30 @@ class ContentProxy
289 $this->updateLanguage($entry, $content['language']); 286 $this->updateLanguage($entry, $content['language']);
290 } 287 }
291 288
292 if (!empty($content['open_graph']['og_image'])) { 289 $previewPictureUrl = '';
293 $this->updatePreviewPicture($entry, $content['open_graph']['og_image']); 290 if (!empty($content['image'])) {
291 $previewPictureUrl = $content['image'];
294 } 292 }
295 293
296 // if content is an image, define it as a preview too 294 // 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)) { 295 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']); 296 $previewPictureUrl = $content['url'];
297 } elseif (empty($previewPictureUrl)) {
298 $this->logger->debug('Extracting images from content to provide a default preview picture');
299 $imagesUrls = DownloadImages::extractImagesUrlsFromHtml($content['html']);
300 $this->logger->debug(\count($imagesUrls) . ' pictures found');
301
302 if (!empty($imagesUrls)) {
303 $previewPictureUrl = $imagesUrls[0];
304 }
305 }
306
307 if (!empty($content['headers']['content-type'])) {
308 $entry->setMimetype($content['headers']['content-type']);
299 } 309 }
300 310
301 if (!empty($content['content_type'])) { 311 if (!empty($previewPictureUrl)) {
302 $entry->setMimetype($content['content_type']); 312 $this->updatePreviewPicture($entry, $previewPictureUrl);
303 } 313 }
304 314
305 try { 315 try {
@@ -316,7 +326,6 @@ class ContentProxy
316 * Update the origin_url field when a redirection occurs 326 * Update the origin_url field when a redirection occurs
317 * This field is set if it is empty and new url does not match ignore list. 327 * This field is set if it is empty and new url does not match ignore list.
318 * 328 *
319 * @param Entry $entry
320 * @param string $url 329 * @param string $url
321 */ 330 */
322 private function updateOriginUrl(Entry $entry, $url) 331 private function updateOriginUrl(Entry $entry, $url)
@@ -424,8 +433,6 @@ class ContentProxy
424 /** 433 /**
425 * Validate that the given content has at least a title, an html and a url. 434 * Validate that the given content has at least a title, an html and a url.
426 * 435 *
427 * @param array $content
428 *
429 * @return bool true if valid otherwise false 436 * @return bool true if valid otherwise false
430 */ 437 */
431 private function validateContent(array $content) 438 private function validateContent(array $content)
diff --git a/src/Wallabag/CoreBundle/Helper/DownloadImages.php b/src/Wallabag/CoreBundle/Helper/DownloadImages.php
index cc3dcfce..1d98fd1a 100644
--- a/src/Wallabag/CoreBundle/Helper/DownloadImages.php
+++ b/src/Wallabag/CoreBundle/Helper/DownloadImages.php
@@ -2,8 +2,15 @@
2 2
3namespace Wallabag\CoreBundle\Helper; 3namespace Wallabag\CoreBundle\Helper;
4 4
5use GuzzleHttp\Client; 5use GuzzleHttp\Psr7\Uri;
6use GuzzleHttp\Message\Response; 6use GuzzleHttp\Psr7\UriResolver;
7use Http\Client\Common\HttpMethodsClient;
8use Http\Client\Common\Plugin\ErrorPlugin;
9use Http\Client\Common\PluginClient;
10use Http\Client\HttpClient;
11use Http\Discovery\MessageFactoryDiscovery;
12use Http\Message\MessageFactory;
13use Psr\Http\Message\ResponseInterface;
7use Psr\Log\LoggerInterface; 14use Psr\Log\LoggerInterface;
8use Symfony\Component\DomCrawler\Crawler; 15use Symfony\Component\DomCrawler\Crawler;
9use Symfony\Component\Finder\Finder; 16use Symfony\Component\Finder\Finder;
@@ -19,9 +26,9 @@ class DownloadImages
19 private $mimeGuesser; 26 private $mimeGuesser;
20 private $wallabagUrl; 27 private $wallabagUrl;
21 28
22 public function __construct(Client $client, $baseFolder, $wallabagUrl, LoggerInterface $logger) 29 public function __construct(HttpClient $client, $baseFolder, $wallabagUrl, LoggerInterface $logger, MessageFactory $messageFactory = null)
23 { 30 {
24 $this->client = $client; 31 $this->client = new HttpMethodsClient(new PluginClient($client, [new ErrorPlugin()]), $messageFactory ?: MessageFactoryDiscovery::find());
25 $this->baseFolder = $baseFolder; 32 $this->baseFolder = $baseFolder;
26 $this->wallabagUrl = rtrim($wallabagUrl, '/'); 33 $this->wallabagUrl = rtrim($wallabagUrl, '/');
27 $this->logger = $logger; 34 $this->logger = $logger;
@@ -31,6 +38,23 @@ class DownloadImages
31 } 38 }
32 39
33 /** 40 /**
41 * Process the html and extract images URLs from it.
42 *
43 * @param string $html
44 *
45 * @return string[]
46 */
47 public static function extractImagesUrlsFromHtml($html)
48 {
49 $crawler = new Crawler($html);
50 $imagesCrawler = $crawler->filterXpath('//img');
51 $imagesUrls = $imagesCrawler->extract(['src']);
52 $imagesSrcsetUrls = self::getSrcsetUrls($imagesCrawler);
53
54 return array_unique(array_merge($imagesUrls, $imagesSrcsetUrls));
55 }
56
57 /**
34 * Process the html and extract image from it, save them to local and return the updated html. 58 * Process the html and extract image from it, save them to local and return the updated html.
35 * 59 *
36 * @param int $entryId ID of the entry 60 * @param int $entryId ID of the entry
@@ -41,13 +65,7 @@ class DownloadImages
41 */ 65 */
42 public function processHtml($entryId, $html, $url) 66 public function processHtml($entryId, $html, $url)
43 { 67 {
44 $crawler = new Crawler($html); 68 $imagesUrls = self::extractImagesUrlsFromHtml($html);
45 $imagesCrawler = $crawler
46 ->filterXpath('//img');
47 $imagesUrls = $imagesCrawler
48 ->extract(['src']);
49 $imagesSrcsetUrls = $this->getSrcsetUrls($imagesCrawler);
50 $imagesUrls = array_unique(array_merge($imagesUrls, $imagesSrcsetUrls));
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':
@@ -181,29 +213,30 @@ class DownloadImages
181 /** 213 /**
182 * Get images urls from the srcset image attribute. 214 * Get images urls from the srcset image attribute.
183 * 215 *
184 * @param Crawler $imagesCrawler
185 *
186 * @return array An array of urls 216 * @return array An array of urls
187 */ 217 */
188 private function getSrcsetUrls(Crawler $imagesCrawler) 218 private static function getSrcsetUrls(Crawler $imagesCrawler)
189 { 219 {
190 $urls = []; 220 $urls = [];
191 $iterator = $imagesCrawler 221 $iterator = $imagesCrawler->getIterator();
192 ->getIterator(); 222
193 while ($iterator->valid()) { 223 while ($iterator->valid()) {
194 $srcsetAttribute = $iterator->current()->getAttribute('srcset'); 224 $srcsetAttribute = $iterator->current()->getAttribute('srcset');
225
195 if ('' !== $srcsetAttribute) { 226 if ('' !== $srcsetAttribute) {
196 // Couldn't start with " OR ' OR a white space 227 // Couldn't start with " OR ' OR a white space
197 // Could be one or more white space 228 // Could be one or more white space
198 // Must be one or more digits followed by w OR x 229 // Must be one or more digits followed by w OR x
199 $pattern = "/(?:[^\"'\s]+\s*(?:\d+[wx])+)/"; 230 $pattern = "/(?:[^\"'\s]+\s*(?:\d+[wx])+)/";
200 preg_match_all($pattern, $srcsetAttribute, $matches); 231 preg_match_all($pattern, $srcsetAttribute, $matches);
232
201 $srcset = \call_user_func_array('array_merge', $matches); 233 $srcset = \call_user_func_array('array_merge', $matches);
202 $srcsetUrls = array_map(function ($src) { 234 $srcsetUrls = array_map(function ($src) {
203 return trim(explode(' ', $src, 2)[0]); 235 return trim(explode(' ', $src, 2)[0]);
204 }, $srcset); 236 }, $srcset);
205 $urls = array_merge($srcsetUrls, $urls); 237 $urls = array_merge($srcsetUrls, $urls);
206 } 238 }
239
207 $iterator->next(); 240 $iterator->next();
208 } 241 }
209 242
@@ -260,33 +293,29 @@ class DownloadImages
260 return $url; 293 return $url;
261 } 294 }
262 295
263 $base = new \SimplePie_IRI($base); 296 $base = new Uri($base);
264 297
265 // remove '//' in URL path (causes URLs not to resolve properly) 298 // in case the url has no scheme & host
266 if (isset($base->ipath)) { 299 if ('' === $base->getAuthority() || '' === $base->getScheme()) {
267 $base->ipath = preg_replace('!//+!', '/', $base->ipath); 300 $this->logger->error('DownloadImages: Can not make an absolute link', ['base' => $base, 'url' => $url]);
268 }
269 301
270 if ($absolute = \SimplePie_IRI::absolutize($base, $url)) { 302 return false;
271 return $absolute->get_uri();
272 } 303 }
273 304
274 $this->logger->error('DownloadImages: Can not make an absolute link', ['base' => $base, 'url' => $url]); 305 return (string) UriResolver::resolve($base, new Uri($url));
275
276 return false;
277 } 306 }
278 307
279 /** 308 /**
280 * Retrieve and validate the extension from the response of the url of the image. 309 * Retrieve and validate the extension from the response of the url of the image.
281 * 310 *
282 * @param Response $res Guzzle Response 311 * @param ResponseInterface $res Http Response
283 * @param string $imagePath Path from the src image from the content (used for log only) 312 * @param string $imagePath Path from the src image from the content (used for log only)
284 * 313 *
285 * @return string|false Extension name or false if validation failed 314 * @return string|false Extension name or false if validation failed
286 */ 315 */
287 private function getExtensionFromResponse(Response $res, $imagePath) 316 private function getExtensionFromResponse(ResponseInterface $res, $imagePath)
288 { 317 {
289 $ext = $this->mimeGuesser->guess($res->getHeader('content-type')); 318 $ext = $this->mimeGuesser->guess(current($res->getHeader('content-type')));
290 $this->logger->debug('DownloadImages: Checking extension', ['ext' => $ext, 'header' => $res->getHeader('content-type')]); 319 $this->logger->debug('DownloadImages: Checking extension', ['ext' => $ext, 'header' => $res->getHeader('content-type')]);
291 320
292 // ok header doesn't have the extension, try a different way 321 // ok header doesn't have the extension, try a different way
diff --git a/src/Wallabag/CoreBundle/Helper/EntriesExport.php b/src/Wallabag/CoreBundle/Helper/EntriesExport.php
index cbf1037b..f981ee50 100644
--- a/src/Wallabag/CoreBundle/Helper/EntriesExport.php
+++ b/src/Wallabag/CoreBundle/Helper/EntriesExport.php
@@ -85,7 +85,7 @@ class EntriesExport
85 public function updateAuthor($method) 85 public function updateAuthor($method)
86 { 86 {
87 if ('entry' !== $method) { 87 if ('entry' !== $method) {
88 $this->author = $method . ' authors'; 88 $this->author = 'Various authors';
89 89
90 return $this; 90 return $this;
91 } 91 }
@@ -150,8 +150,6 @@ class EntriesExport
150 */ 150 */
151 151
152 $book->setTitle($this->title); 152 $book->setTitle($this->title);
153 // Could also be the ISBN number, prefered for published books, or a UUID.
154 $book->setIdentifier($this->title, EPub::IDENTIFIER_URI);
155 // Not needed, but included for the example, Language is mandatory, but EPub defaults to "en". Use RFC3066 Language codes, such as "en", "da", "fr" etc. 153 // Not needed, but included for the example, Language is mandatory, but EPub defaults to "en". Use RFC3066 Language codes, such as "en", "da", "fr" etc.
156 $book->setLanguage($this->language); 154 $book->setLanguage($this->language);
157 $book->setDescription('Some articles saved on my wallabag'); 155 $book->setDescription('Some articles saved on my wallabag');
@@ -167,12 +165,9 @@ class EntriesExport
167 $book->addDublinCoreMetadata(DublinCore::CONTRIBUTOR, 'PHP'); 165 $book->addDublinCoreMetadata(DublinCore::CONTRIBUTOR, 'PHP');
168 $book->addDublinCoreMetadata(DublinCore::CONTRIBUTOR, 'wallabag'); 166 $book->addDublinCoreMetadata(DublinCore::CONTRIBUTOR, 'wallabag');
169 167
170 /* 168 $entryIds = [];
171 * Front page 169 $entryCount = \count($this->entries);
172 */ 170 $i = 0;
173 if (file_exists($this->logoPath)) {
174 $book->setCoverImage('Cover.png', file_get_contents($this->logoPath), 'image/png');
175 }
176 171
177 /* 172 /*
178 * Adding actual entries 173 * Adding actual entries
@@ -180,21 +175,48 @@ class EntriesExport
180 175
181 // set tags as subjects 176 // set tags as subjects
182 foreach ($this->entries as $entry) { 177 foreach ($this->entries as $entry) {
178 ++$i;
179
180 /*
181 * Front page
182 * Set if there's only one entry in the given set
183 */
184 if (1 === $entryCount && null !== $entry->getPreviewPicture()) {
185 $book->setCoverImage($entry->getPreviewPicture());
186 }
187
183 foreach ($entry->getTags() as $tag) { 188 foreach ($entry->getTags() as $tag) {
184 $book->setSubject($tag->getLabel()); 189 $book->setSubject($tag->getLabel());
185 } 190 }
191 $filename = sha1(sprintf('%s:%s', $entry->getUrl(), $entry->getTitle()));
186 192
187 // the reader in Kobo Devices doesn't likes special caracters 193 $publishedBy = $entry->getPublishedBy();
188 // in filenames, we limit to A-z/0-9 194 $authors = $this->translator->trans('export.unknown');
189 $filename = preg_replace('/[^A-Za-z0-9\-]/', '', $entry->getTitle()); 195 if (!empty($publishedBy)) {
196 $authors = implode(',', $publishedBy);
197 }
190 198
191 $titlepage = $content_start . '<h1>' . $entry->getTitle() . '</h1>' . $this->getExportInformation('PHPePub') . $bookEnd; 199 $titlepage = $content_start .
192 $book->addChapter('Title', 'Title.html', $titlepage, true, EPub::EXTERNAL_REF_ADD); 200 '<h1>' . $entry->getTitle() . '</h1>' .
201 '<dl>' .
202 '<dt>' . $this->translator->trans('entry.view.published_by') . '</dt><dd>' . $authors . '</dd>' .
203 '<dt>' . $this->translator->trans('entry.metadata.reading_time') . '</dt><dd>' . $this->translator->trans('entry.metadata.reading_time_minutes_short', ['%readingTime%' => $entry->getReadingTime()]) . '</dd>' .
204 '<dt>' . $this->translator->trans('entry.metadata.added_on') . '</dt><dd>' . $entry->getCreatedAt()->format('Y-m-d') . '</dd>' .
205 '<dt>' . $this->translator->trans('entry.metadata.address') . '</dt><dd><a href="' . $entry->getUrl() . '">' . $entry->getUrl() . '</a></dd>' .
206 '</dl>' .
207 $bookEnd;
208 $book->addChapter("Entry {$i} of {$entryCount}", "{$filename}_cover.html", $titlepage, true, EPub::EXTERNAL_REF_ADD);
193 $chapter = $content_start . $entry->getContent() . $bookEnd; 209 $chapter = $content_start . $entry->getContent() . $bookEnd;
194 $book->addChapter($entry->getTitle(), htmlspecialchars($filename) . '.html', $chapter, true, EPub::EXTERNAL_REF_ADD); 210
211 $entryIds[] = $entry->getId();
212 $book->addChapter($entry->getTitle(), "{$filename}.html", $chapter, true, EPub::EXTERNAL_REF_ADD);
195 } 213 }
196 214
197 $book->buildTOC(); 215 $book->addChapter('Notices', 'Cover2.html', $content_start . $this->getExportInformation('PHPePub') . $bookEnd);
216
217 // Could also be the ISBN number, prefered for published books, or a UUID.
218 $hash = sha1(sprintf('%s:%s', $this->wallabagUrl, implode(',', $entryIds)));
219 $book->setIdentifier(sprintf('urn:wallabag:%s', $hash), EPub::IDENTIFIER_URI);
198 220
199 return Response::create( 221 return Response::create(
200 $book->getBook(), 222 $book->getBook(),
@@ -202,7 +224,7 @@ class EntriesExport
202 [ 224 [
203 'Content-Description' => 'File Transfer', 225 'Content-Description' => 'File Transfer',
204 'Content-type' => 'application/epub+zip', 226 'Content-type' => 'application/epub+zip',
205 'Content-Disposition' => 'attachment; filename="' . $this->title . '.epub"', 227 'Content-Disposition' => 'attachment; filename="' . $this->getSanitizedFilename() . '.epub"',
206 'Content-Transfer-Encoding' => 'binary', 228 'Content-Transfer-Encoding' => 'binary',
207 ] 229 ]
208 ); 230 );
@@ -244,9 +266,6 @@ class EntriesExport
244 } 266 }
245 $mobi->setContentProvider($content); 267 $mobi->setContentProvider($content);
246 268
247 // the browser inside Kindle Devices doesn't likes special caracters either, we limit to A-z/0-9
248 $this->title = preg_replace('/[^A-Za-z0-9\-]/', '', $this->title);
249
250 return Response::create( 269 return Response::create(
251 $mobi->toString(), 270 $mobi->toString(),
252 200, 271 200,
@@ -254,7 +273,7 @@ class EntriesExport
254 'Accept-Ranges' => 'bytes', 273 'Accept-Ranges' => 'bytes',
255 'Content-Description' => 'File Transfer', 274 'Content-Description' => 'File Transfer',
256 'Content-type' => 'application/x-mobipocket-ebook', 275 'Content-type' => 'application/x-mobipocket-ebook',
257 'Content-Disposition' => 'attachment; filename="' . $this->title . '.mobi"', 276 'Content-Disposition' => 'attachment; filename="' . $this->getSanitizedFilename() . '.mobi"',
258 'Content-Transfer-Encoding' => 'binary', 277 'Content-Transfer-Encoding' => 'binary',
259 ] 278 ]
260 ); 279 );
@@ -279,14 +298,6 @@ class EntriesExport
279 $pdf->SetKeywords('wallabag'); 298 $pdf->SetKeywords('wallabag');
280 299
281 /* 300 /*
282 * Front page
283 */
284 $pdf->AddPage();
285 $intro = '<h1>' . $this->title . '</h1>' . $this->getExportInformation('tcpdf');
286
287 $pdf->writeHTMLCell(0, 0, '', '', $intro, 0, 1, 0, true, '', true);
288
289 /*
290 * Adding actual entries 301 * Adding actual entries
291 */ 302 */
292 foreach ($this->entries as $entry) { 303 foreach ($this->entries as $entry) {
@@ -294,6 +305,22 @@ class EntriesExport
294 $pdf->SetKeywords($tag->getLabel()); 305 $pdf->SetKeywords($tag->getLabel());
295 } 306 }
296 307
308 $publishedBy = $entry->getPublishedBy();
309 $authors = $this->translator->trans('export.unknown');
310 if (!empty($publishedBy)) {
311 $authors = implode(',', $publishedBy);
312 }
313
314 $pdf->addPage();
315 $html = '<h1>' . $entry->getTitle() . '</h1>' .
316 '<dl>' .
317 '<dt>' . $this->translator->trans('entry.view.published_by') . '</dt><dd>' . $authors . '</dd>' .
318 '<dt>' . $this->translator->trans('entry.metadata.reading_time') . '</dt><dd>' . $this->translator->trans('entry.metadata.reading_time_minutes_short', ['%readingTime%' => $entry->getReadingTime()]) . '</dd>' .
319 '<dt>' . $this->translator->trans('entry.metadata.added_on') . '</dt><dd>' . $entry->getCreatedAt()->format('Y-m-d') . '</dd>' .
320 '<dt>' . $this->translator->trans('entry.metadata.address') . '</dt><dd><a href="' . $entry->getUrl() . '">' . $entry->getUrl() . '</a></dd>' .
321 '</dl>';
322 $pdf->writeHTMLCell(0, 0, '', '', $html, 0, 1, 0, true, '', true);
323
297 $pdf->AddPage(); 324 $pdf->AddPage();
298 $html = '<h1>' . $entry->getTitle() . '</h1>'; 325 $html = '<h1>' . $entry->getTitle() . '</h1>';
299 $html .= $entry->getContent(); 326 $html .= $entry->getContent();
@@ -301,6 +328,14 @@ class EntriesExport
301 $pdf->writeHTMLCell(0, 0, '', '', $html, 0, 1, 0, true, '', true); 328 $pdf->writeHTMLCell(0, 0, '', '', $html, 0, 1, 0, true, '', true);
302 } 329 }
303 330
331 /*
332 * Last page
333 */
334 $pdf->AddPage();
335 $html = $this->getExportInformation('tcpdf');
336
337 $pdf->writeHTMLCell(0, 0, '', '', $html, 0, 1, 0, true, '', true);
338
304 // set image scale factor 339 // set image scale factor
305 $pdf->setImageScale(PDF_IMAGE_SCALE_RATIO); 340 $pdf->setImageScale(PDF_IMAGE_SCALE_RATIO);
306 341
@@ -310,7 +345,7 @@ class EntriesExport
310 [ 345 [
311 'Content-Description' => 'File Transfer', 346 'Content-Description' => 'File Transfer',
312 'Content-type' => 'application/pdf', 347 'Content-type' => 'application/pdf',
313 'Content-Disposition' => 'attachment; filename="' . $this->title . '.pdf"', 348 'Content-Disposition' => 'attachment; filename="' . $this->getSanitizedFilename() . '.pdf"',
314 'Content-Transfer-Encoding' => 'binary', 349 'Content-Transfer-Encoding' => 'binary',
315 ] 350 ]
316 ); 351 );
@@ -356,7 +391,7 @@ class EntriesExport
356 200, 391 200,
357 [ 392 [
358 'Content-type' => 'application/csv', 393 'Content-type' => 'application/csv',
359 'Content-Disposition' => 'attachment; filename="' . $this->title . '.csv"', 394 'Content-Disposition' => 'attachment; filename="' . $this->getSanitizedFilename() . '.csv"',
360 'Content-Transfer-Encoding' => 'UTF-8', 395 'Content-Transfer-Encoding' => 'UTF-8',
361 ] 396 ]
362 ); 397 );
@@ -374,7 +409,7 @@ class EntriesExport
374 200, 409 200,
375 [ 410 [
376 'Content-type' => 'application/json', 411 'Content-type' => 'application/json',
377 'Content-Disposition' => 'attachment; filename="' . $this->title . '.json"', 412 'Content-Disposition' => 'attachment; filename="' . $this->getSanitizedFilename() . '.json"',
378 'Content-Transfer-Encoding' => 'UTF-8', 413 'Content-Transfer-Encoding' => 'UTF-8',
379 ] 414 ]
380 ); 415 );
@@ -392,7 +427,7 @@ class EntriesExport
392 200, 427 200,
393 [ 428 [
394 'Content-type' => 'application/xml', 429 'Content-type' => 'application/xml',
395 'Content-Disposition' => 'attachment; filename="' . $this->title . '.xml"', 430 'Content-Disposition' => 'attachment; filename="' . $this->getSanitizedFilename() . '.xml"',
396 'Content-Transfer-Encoding' => 'UTF-8', 431 'Content-Transfer-Encoding' => 'UTF-8',
397 ] 432 ]
398 ); 433 );
@@ -418,7 +453,7 @@ class EntriesExport
418 200, 453 200,
419 [ 454 [
420 'Content-type' => 'text/plain', 455 'Content-type' => 'text/plain',
421 'Content-Disposition' => 'attachment; filename="' . $this->title . '.txt"', 456 'Content-Disposition' => 'attachment; filename="' . $this->getSanitizedFilename() . '.txt"',
422 'Content-Transfer-Encoding' => 'UTF-8', 457 'Content-Transfer-Encoding' => 'UTF-8',
423 ] 458 ]
424 ); 459 );
@@ -461,4 +496,15 @@ class EntriesExport
461 496
462 return str_replace('%IMAGE%', '', $info); 497 return str_replace('%IMAGE%', '', $info);
463 } 498 }
499
500 /**
501 * Return a sanitized version of the title by applying translit iconv
502 * and removing non alphanumeric characters, - and space.
503 *
504 * @return string Sanitized filename
505 */
506 private function getSanitizedFilename()
507 {
508 return preg_replace('/[^A-Za-z0-9\- \']/', '', iconv('utf-8', 'us-ascii//TRANSLIT', $this->title));
509 }
464} 510}
diff --git a/src/Wallabag/CoreBundle/Helper/FileCookieJar.php b/src/Wallabag/CoreBundle/Helper/FileCookieJar.php
new file mode 100644
index 00000000..9a63e949
--- /dev/null
+++ b/src/Wallabag/CoreBundle/Helper/FileCookieJar.php
@@ -0,0 +1,91 @@
1<?php
2
3namespace Wallabag\CoreBundle\Helper;
4
5use GuzzleHttp\Cookie\FileCookieJar as BaseFileCookieJar;
6use GuzzleHttp\Cookie\SetCookie;
7use GuzzleHttp\Utils;
8use Psr\Log\LoggerInterface;
9
10/**
11 * Overidden Cookie behavior to:
12 * - fix multiple concurrent writes (see https://github.com/guzzle/guzzle/pull/1884)
13 * - ignore error when the cookie file is malformatted (resulting in clearing it).
14 */
15class FileCookieJar extends BaseFileCookieJar
16{
17 private $logger;
18
19 /**
20 * @param LoggerInterface $logger Only used to log info when something goes wrong
21 * @param string $cookieFile File to store the cookie data
22 */
23 public function __construct(LoggerInterface $logger, $cookieFile)
24 {
25 parent::__construct($cookieFile);
26
27 $this->logger = $logger;
28 }
29
30 /**
31 * Saves the cookies to a file.
32 *
33 * @param string $filename File to save
34 *
35 * @throws \RuntimeException if the file cannot be found or created
36 */
37 public function save($filename)
38 {
39 $json = [];
40 foreach ($this as $cookie) {
41 if ($cookie->getExpires() && !$cookie->getDiscard()) {
42 $json[] = $cookie->toArray();
43 }
44 }
45
46 if (false === file_put_contents($filename, json_encode($json), LOCK_EX)) {
47 // @codeCoverageIgnoreStart
48 throw new \RuntimeException("Unable to save file {$filename}");
49 // @codeCoverageIgnoreEnd
50 }
51 }
52
53 /**
54 * Load cookies from a JSON formatted file.
55 *
56 * Old cookies are kept unless overwritten by newly loaded ones.
57 *
58 * @param string $filename cookie file to load
59 *
60 * @throws \RuntimeException if the file cannot be loaded
61 */
62 public function load($filename)
63 {
64 $json = file_get_contents($filename);
65 if (false === $json) {
66 // @codeCoverageIgnoreStart
67 throw new \RuntimeException("Unable to load file {$filename}");
68 // @codeCoverageIgnoreEnd
69 }
70
71 try {
72 $data = Utils::jsonDecode($json, true);
73 } catch (\InvalidArgumentException $e) {
74 $this->logger->error('JSON inside the cookie is broken', [
75 'json' => $json,
76 'error_msg' => $e->getMessage(),
77 ]);
78
79 // cookie file is invalid, just ignore the exception and it'll reset the whole cookie file
80 $data = '';
81 }
82
83 if (\is_array($data)) {
84 foreach (Utils::jsonDecode($json, true) as $cookie) {
85 $this->setCookie(new SetCookie($cookie));
86 }
87 } elseif (\strlen($data)) {
88 throw new \RuntimeException("Invalid cookie file: {$filename}");
89 }
90 }
91}
diff --git a/src/Wallabag/CoreBundle/Helper/HttpClientFactory.php b/src/Wallabag/CoreBundle/Helper/HttpClientFactory.php
index 4602a684..ea864acb 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 = [];
@@ -25,9 +27,7 @@ class HttpClientFactory
25 /** 27 /**
26 * HttpClientFactory constructor. 28 * HttpClientFactory constructor.
27 * 29 *
28 * @param \GuzzleHttp\Cookie\CookieJar $cookieJar 30 * @param string $restrictedAccess This param is a kind of boolean. Values: 0 or 1
29 * @param string $restrictedAccess This param is a kind of boolean. Values: 0 or 1
30 * @param LoggerInterface $logger
31 */ 31 */
32 public function __construct(CookieJar $cookieJar, $restrictedAccess, LoggerInterface $logger) 32 public function __construct(CookieJar $cookieJar, $restrictedAccess, LoggerInterface $logger)
33 { 33 {
@@ -37,35 +37,38 @@ class HttpClientFactory
37 } 37 }
38 38
39 /** 39 /**
40 * @return \GuzzleHttp\Client|null 40 * Adds a subscriber to the HTTP client.
41 */
42 public function addSubscriber(SubscriberInterface $subscriber)
43 {
44 $this->subscribers[] = $subscriber;
45 }
46
47 /**
48 * Input an array of configuration to be able to create a HttpClient.
49 *
50 * @return HttpClient
41 */ 51 */
42 public function buildHttpClient() 52 public function createClient(array $config = [])
43 { 53 {
44 $this->logger->log('debug', 'Restricted access config enabled?', ['enabled' => (int) $this->restrictedAccess]); 54 $this->logger->log('debug', 'Restricted access config enabled?', ['enabled' => (int) $this->restrictedAccess]);
45 55
46 if (0 === (int) $this->restrictedAccess) { 56 if (0 === (int) $this->restrictedAccess) {
47 return; 57 return new GuzzleAdapter(new GuzzleClient($config));
48 } 58 }
49 59
50 // we clear the cookie to avoid websites who use cookies for analytics 60 // we clear the cookie to avoid websites who use cookies for analytics
51 $this->cookieJar->clear(); 61 $this->cookieJar->clear();
52 // need to set the (shared) cookie jar 62 if (!isset($config['defaults']['cookies'])) {
53 $client = new Client(['handler' => new SafeCurlHandler(), 'defaults' => ['cookies' => $this->cookieJar]]); 63 // need to set the (shared) cookie jar
64 $config['defaults']['cookies'] = $this->cookieJar;
65 }
54 66
67 $guzzle = new GuzzleClient($config);
55 foreach ($this->subscribers as $subscriber) { 68 foreach ($this->subscribers as $subscriber) {
56 $client->getEmitter()->attach($subscriber); 69 $guzzle->getEmitter()->attach($subscriber);
57 } 70 }
58 71
59 return $client; 72 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 } 73 }
71} 74}
diff --git a/src/Wallabag/CoreBundle/Helper/PreparePagerForEntries.php b/src/Wallabag/CoreBundle/Helper/PreparePagerForEntries.php
index 1c2c5093..3d56a6d8 100644
--- a/src/Wallabag/CoreBundle/Helper/PreparePagerForEntries.php
+++ b/src/Wallabag/CoreBundle/Helper/PreparePagerForEntries.php
@@ -20,10 +20,9 @@ class PreparePagerForEntries
20 } 20 }
21 21
22 /** 22 /**
23 * @param AdapterInterface $adapter 23 * @param User $user If user isn't logged in, we can force it (like for feed)
24 * @param User $user If user isn't logged in, we can force it (like for rss)
25 * 24 *
26 * @return null|Pagerfanta 25 * @return Pagerfanta|null
27 */ 26 */
28 public function prepare(AdapterInterface $adapter, User $user = null) 27 public function prepare(AdapterInterface $adapter, User $user = null)
29 { 28 {
diff --git a/src/Wallabag/CoreBundle/Helper/RuleBasedTagger.php b/src/Wallabag/CoreBundle/Helper/RuleBasedTagger.php
index 63f65067..d48e2469 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;
@@ -55,8 +56,6 @@ class RuleBasedTagger
55 /** 56 /**
56 * Apply all the tagging rules defined by a user on its entries. 57 * Apply all the tagging rules defined by a user on its entries.
57 * 58 *
58 * @param User $user
59 *
60 * @return array<Entry> A list of modified entries 59 * @return array<Entry> A list of modified entries
61 */ 60 */
62 public function tagAllForUser(User $user) 61 public function tagAllForUser(User $user)
@@ -108,8 +107,6 @@ class RuleBasedTagger
108 /** 107 /**
109 * Retrieves the tagging rules for a given user. 108 * Retrieves the tagging rules for a given user.
110 * 109 *
111 * @param User $user
112 *
113 * @return array<TaggingRule> 110 * @return array<TaggingRule>
114 */ 111 */
115 private function getRulesForUser(User $user) 112 private function getRulesForUser(User $user)
diff --git a/src/Wallabag/CoreBundle/Helper/TagsAssigner.php b/src/Wallabag/CoreBundle/Helper/TagsAssigner.php
index e6b4989f..433b09fe 100644
--- a/src/Wallabag/CoreBundle/Helper/TagsAssigner.php
+++ b/src/Wallabag/CoreBundle/Helper/TagsAssigner.php
@@ -21,7 +21,6 @@ class TagsAssigner
21 /** 21 /**
22 * Assign some tags to an entry. 22 * Assign some tags to an entry.
23 * 23 *
24 * @param Entry $entry
25 * @param array|string $tags An array of tag or a string coma separated of tag 24 * @param array|string $tags An array of tag or a string coma separated of tag
26 * @param array $entitiesReady Entities from the EntityManager which are persisted but not yet flushed 25 * @param array $entitiesReady Entities from the EntityManager which are persisted but not yet flushed
27 * It is mostly to fix duplicate tag on import @see http://stackoverflow.com/a/7879164/569101 26 * It is mostly to fix duplicate tag on import @see http://stackoverflow.com/a/7879164/569101
diff --git a/src/Wallabag/CoreBundle/Helper/UrlHasher.php b/src/Wallabag/CoreBundle/Helper/UrlHasher.php
new file mode 100644
index 00000000..6753745f
--- /dev/null
+++ b/src/Wallabag/CoreBundle/Helper/UrlHasher.php
@@ -0,0 +1,22 @@
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 $algorithm
15 *
16 * @return string
17 */
18 public static function hashUrl(string $url, $algorithm = 'sha1')
19 {
20 return hash($algorithm, urldecode($url));
21 }
22}
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 83379998..bfd07937 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 /**
@@ -129,6 +130,21 @@ class EntryRepository extends EntityRepository
129 } 130 }
130 131
131 /** 132 /**
133 * Retrieve the number of untagged entries for a user.
134 *
135 * @param int $userId
136 *
137 * @return int
138 */
139 public function countUntaggedEntriesByUser($userId)
140 {
141 return (int) $this->getRawBuilderForUntaggedByUser($userId)
142 ->select('count(e.id)')
143 ->getQuery()
144 ->getSingleScalarResult();
145 }
146
147 /**
132 * Find Entries. 148 * Find Entries.
133 * 149 *
134 * @param int $userId 150 * @param int $userId
@@ -139,15 +155,30 @@ class EntryRepository extends EntityRepository
139 * @param string $order 155 * @param string $order
140 * @param int $since 156 * @param int $since
141 * @param string $tags 157 * @param string $tags
158 * @param string $detail 'metadata' or 'full'. Include content field if 'full'
159 *
160 * @todo Breaking change: replace default detail=full by detail=metadata in a future version
142 * 161 *
143 * @return Pagerfanta 162 * @return Pagerfanta
144 */ 163 */
145 public function findEntries($userId, $isArchived = null, $isStarred = null, $isPublic = null, $sort = 'created', $order = 'ASC', $since = 0, $tags = '') 164 public function findEntries($userId, $isArchived = null, $isStarred = null, $isPublic = null, $sort = 'created', $order = 'asc', $since = 0, $tags = '', $detail = 'full')
146 { 165 {
166 if (!\in_array(strtolower($detail), ['full', 'metadata'], true)) {
167 throw new \Exception('Detail "' . $detail . '" parameter is wrong, allowed: full or metadata');
168 }
169
147 $qb = $this->createQueryBuilder('e') 170 $qb = $this->createQueryBuilder('e')
148 ->leftJoin('e.tags', 't') 171 ->leftJoin('e.tags', 't')
149 ->where('e.user = :userId')->setParameter('userId', $userId); 172 ->where('e.user = :userId')->setParameter('userId', $userId);
150 173
174 if ('metadata' === $detail) {
175 $fieldNames = $this->getClassMetadata()->getFieldNames();
176 $fields = array_filter($fieldNames, function ($k) {
177 return 'content' !== $k;
178 });
179 $qb->select(sprintf('partial e.{%s}', implode(',', $fields)));
180 }
181
151 if (null !== $isArchived) { 182 if (null !== $isArchived) {
152 $qb->andWhere('e.isArchived = :isArchived')->setParameter('isArchived', (bool) $isArchived); 183 $qb->andWhere('e.isArchived = :isArchived')->setParameter('isArchived', (bool) $isArchived);
153 } 184 }
@@ -185,10 +216,16 @@ class EntryRepository extends EntityRepository
185 } 216 }
186 } 217 }
187 218
219 if (!\in_array(strtolower($order), ['asc', 'desc'], true)) {
220 throw new \Exception('Order "' . $order . '" parameter is wrong, allowed: asc or desc');
221 }
222
188 if ('created' === $sort) { 223 if ('created' === $sort) {
189 $qb->orderBy('e.id', $order); 224 $qb->orderBy('e.id', $order);
190 } elseif ('updated' === $sort) { 225 } elseif ('updated' === $sort) {
191 $qb->orderBy('e.updatedAt', $order); 226 $qb->orderBy('e.updatedAt', $order);
227 } elseif ('archived' === $sort) {
228 $qb->orderBy('e.archivedAt', $order);
192 } 229 }
193 230
194 $pagerAdapter = new DoctrineORMAdapter($qb, true, false); 231 $pagerAdapter = new DoctrineORMAdapter($qb, true, false);
@@ -269,7 +306,6 @@ class EntryRepository extends EntityRepository
269 * DELETE et FROM entry_tag et WHERE et.entry_id IN ( SELECT e.id FROM entry e WHERE e.user_id = :userId ) AND et.tag_id = :tagId 306 * DELETE et FROM entry_tag et WHERE et.entry_id IN ( SELECT e.id FROM entry e WHERE e.user_id = :userId ) AND et.tag_id = :tagId
270 * 307 *
271 * @param int $userId 308 * @param int $userId
272 * @param Tag $tag
273 */ 309 */
274 public function removeTag($userId, Tag $tag) 310 public function removeTag($userId, Tag $tag)
275 { 311 {
@@ -320,15 +356,44 @@ class EntryRepository extends EntityRepository
320 * Find an entry by its url and its owner. 356 * Find an entry by its url and its owner.
321 * If it exists, return the entry otherwise return false. 357 * If it exists, return the entry otherwise return false.
322 * 358 *
323 * @param $url 359 * @param string $url
324 * @param $userId 360 * @param int $userId
325 * 361 *
326 * @return Entry|bool 362 * @return Entry|false
327 */ 363 */
328 public function findByUrlAndUserId($url, $userId) 364 public function findByUrlAndUserId($url, $userId)
329 { 365 {
366 return $this->findByHashedUrlAndUserId(
367 UrlHasher::hashUrl($url),
368 $userId
369 );
370 }
371
372 /**
373 * Find an entry by its hashed url and its owner.
374 * If it exists, return the entry otherwise return false.
375 *
376 * @param string $hashedUrl Url hashed using sha1
377 * @param int $userId
378 *
379 * @return Entry|false
380 */
381 public function findByHashedUrlAndUserId($hashedUrl, $userId)
382 {
383 // try first using hashed_url (to use the database index)
330 $res = $this->createQueryBuilder('e') 384 $res = $this->createQueryBuilder('e')
331 ->where('e.url = :url')->setParameter('url', urldecode($url)) 385 ->where('e.hashedUrl = :hashed_url')->setParameter('hashed_url', $hashedUrl)
386 ->andWhere('e.user = :user_id')->setParameter('user_id', $userId)
387 ->getQuery()
388 ->getResult();
389
390 if (\count($res)) {
391 return current($res);
392 }
393
394 // then try using hashed_given_url (to use the database index)
395 $res = $this->createQueryBuilder('e')
396 ->where('e.hashedGivenUrl = :hashed_given_url')->setParameter('hashed_given_url', $hashedUrl)
332 ->andWhere('e.user = :user_id')->setParameter('user_id', $userId) 397 ->andWhere('e.user = :user_id')->setParameter('user_id', $userId)
333 ->getQuery() 398 ->getQuery()
334 ->getResult(); 399 ->getResult();
@@ -412,8 +477,8 @@ class EntryRepository extends EntityRepository
412 /** 477 /**
413 * Find all entries by url and owner. 478 * Find all entries by url and owner.
414 * 479 *
415 * @param $url 480 * @param string $url
416 * @param $userId 481 * @param int $userId
417 * 482 *
418 * @return array 483 * @return array
419 */ 484 */
@@ -427,6 +492,49 @@ class EntryRepository extends EntityRepository
427 } 492 }
428 493
429 /** 494 /**
495 * Returns a random entry, filtering by status.
496 *
497 * @param int $userId
498 * @param string $type Can be unread, archive, starred, etc
499 *
500 * @throws NoResultException
501 *
502 * @return Entry
503 */
504 public function getRandomEntry($userId, $type = '')
505 {
506 $qb = $this->getQueryBuilderByUser($userId)
507 ->select('e.id');
508
509 switch ($type) {
510 case 'unread':
511 $qb->andWhere('e.isArchived = false');
512 break;
513 case 'archive':
514 $qb->andWhere('e.isArchived = true');
515 break;
516 case 'starred':
517 $qb->andWhere('e.isStarred = true');
518 break;
519 case 'untagged':
520 $qb->leftJoin('e.tags', 't');
521 $qb->andWhere('t.id is null');
522 break;
523 }
524
525 $ids = $qb->getQuery()->getArrayResult();
526
527 if (empty($ids)) {
528 throw new NoResultException();
529 }
530
531 // random select one in the list
532 $randomId = $ids[mt_rand(0, \count($ids) - 1)]['id'];
533
534 return $this->find($randomId);
535 }
536
537 /**
430 * Return a query builder to be used by other getBuilderFor* method. 538 * Return a query builder to be used by other getBuilderFor* method.
431 * 539 *
432 * @param int $userId 540 * @param int $userId
@@ -456,15 +564,13 @@ class EntryRepository extends EntityRepository
456 /** 564 /**
457 * Return the given QueryBuilder with an orderBy() call. 565 * Return the given QueryBuilder with an orderBy() call.
458 * 566 *
459 * @param QueryBuilder $qb 567 * @param string $sortBy
460 * @param string $sortBy 568 * @param string $direction
461 * @param string $direction
462 * 569 *
463 * @return QueryBuilder 570 * @return QueryBuilder
464 */ 571 */
465 private function sortQueryBuilder(QueryBuilder $qb, $sortBy = 'createdAt', $direction = 'desc') 572 private function sortQueryBuilder(QueryBuilder $qb, $sortBy = 'createdAt', $direction = 'desc')
466 { 573 {
467 return $qb 574 return $qb->orderBy(sprintf('e.%s', $sortBy), $direction);
468 ->orderBy(sprintf('e.%s', $sortBy), $direction);
469 } 575 }
470} 576}
diff --git a/src/Wallabag/CoreBundle/Repository/SiteCredentialRepository.php b/src/Wallabag/CoreBundle/Repository/SiteCredentialRepository.php
index 36906761..aeadd770 100644
--- a/src/Wallabag/CoreBundle/Repository/SiteCredentialRepository.php
+++ b/src/Wallabag/CoreBundle/Repository/SiteCredentialRepository.php
@@ -19,16 +19,16 @@ class SiteCredentialRepository extends \Doctrine\ORM\EntityRepository
19 /** 19 /**
20 * Retrieve one username/password for the given host and userId. 20 * Retrieve one username/password for the given host and userId.
21 * 21 *
22 * @param string $host 22 * @param array $hosts An array of host to look for
23 * @param int $userId 23 * @param int $userId
24 * 24 *
25 * @return null|array 25 * @return array|null
26 */ 26 */
27 public function findOneByHostAndUser($host, $userId) 27 public function findOneByHostsAndUser($hosts, $userId)
28 { 28 {
29 $res = $this->createQueryBuilder('s') 29 $res = $this->createQueryBuilder('s')
30 ->select('s.username', 's.password') 30 ->select('s.username', 's.password')
31 ->where('s.host = :hostname')->setParameter('hostname', $host) 31 ->where('s.host IN (:hosts)')->setParameter('hosts', $hosts)
32 ->andWhere('s.user = :userId')->setParameter('userId', $userId) 32 ->andWhere('s.user = :userId')->setParameter('userId', $userId)
33 ->setMaxResults(1) 33 ->setMaxResults(1)
34 ->getQuery() 34 ->getQuery()
diff --git a/src/Wallabag/CoreBundle/Repository/TagRepository.php b/src/Wallabag/CoreBundle/Repository/TagRepository.php
index 3ae9d414..8464a6a5 100644
--- a/src/Wallabag/CoreBundle/Repository/TagRepository.php
+++ b/src/Wallabag/CoreBundle/Repository/TagRepository.php
@@ -3,6 +3,7 @@
3namespace Wallabag\CoreBundle\Repository; 3namespace Wallabag\CoreBundle\Repository;
4 4
5use Doctrine\ORM\EntityRepository; 5use Doctrine\ORM\EntityRepository;
6use Doctrine\ORM\QueryBuilder;
6use Wallabag\CoreBundle\Entity\Tag; 7use Wallabag\CoreBundle\Entity\Tag;
7 8
8class TagRepository extends EntityRepository 9class TagRepository extends EntityRepository
@@ -45,12 +46,8 @@ class TagRepository extends EntityRepository
45 */ 46 */
46 public function findAllTags($userId) 47 public function findAllTags($userId)
47 { 48 {
48 $ids = $this->createQueryBuilder('t') 49 $ids = $this->getQueryBuilderByUser($userId)
49 ->select('t.id') 50 ->select('t.id')
50 ->leftJoin('t.entries', 'e')
51 ->where('e.user = :userId')->setParameter('userId', $userId)
52 ->groupBy('t.id')
53 ->orderBy('t.slug')
54 ->getQuery() 51 ->getQuery()
55 ->getArrayResult(); 52 ->getArrayResult();
56 53
@@ -71,18 +68,30 @@ class TagRepository extends EntityRepository
71 */ 68 */
72 public function findAllFlatTagsWithNbEntries($userId) 69 public function findAllFlatTagsWithNbEntries($userId)
73 { 70 {
74 return $this->createQueryBuilder('t') 71 return $this->getQueryBuilderByUser($userId)
75 ->select('t.id, t.label, t.slug, count(e.id) as nbEntries') 72 ->select('t.id, t.label, t.slug, count(e.id) as nbEntries')
76 ->distinct(true) 73 ->distinct(true)
77 ->leftJoin('t.entries', 'e')
78 ->where('e.user = :userId')
79 ->groupBy('t.id')
80 ->orderBy('t.slug')
81 ->setParameter('userId', $userId)
82 ->getQuery() 74 ->getQuery()
83 ->getArrayResult(); 75 ->getArrayResult();
84 } 76 }
85 77
78 public function findByLabelsAndUser($labels, $userId)
79 {
80 $qb = $this->getQueryBuilderByUser($userId)
81 ->select('t.id');
82
83 $ids = $qb->andWhere($qb->expr()->in('t.label', $labels))
84 ->getQuery()
85 ->getArrayResult();
86
87 $tags = [];
88 foreach ($ids as $id) {
89 $tags[] = $this->find($id);
90 }
91
92 return $tags;
93 }
94
86 /** 95 /**
87 * Used only in test case to get a tag for our entry. 96 * Used only in test case to get a tag for our entry.
88 * 97 *
@@ -101,13 +110,9 @@ class TagRepository extends EntityRepository
101 110
102 public function findForArchivedArticlesByUser($userId) 111 public function findForArchivedArticlesByUser($userId)
103 { 112 {
104 $ids = $this->createQueryBuilder('t') 113 $ids = $this->getQueryBuilderByUser($userId)
105 ->select('t.id') 114 ->select('t.id')
106 ->leftJoin('t.entries', 'e')
107 ->where('e.user = :userId')->setParameter('userId', $userId)
108 ->andWhere('e.isArchived = true') 115 ->andWhere('e.isArchived = true')
109 ->groupBy('t.id')
110 ->orderBy('t.slug')
111 ->getQuery() 116 ->getQuery()
112 ->getArrayResult(); 117 ->getArrayResult();
113 118
@@ -118,4 +123,20 @@ class TagRepository extends EntityRepository
118 123
119 return $tags; 124 return $tags;
120 } 125 }
126
127 /**
128 * Retrieve a sorted list of tags used by a user.
129 *
130 * @param int $userId
131 *
132 * @return QueryBuilder
133 */
134 private function getQueryBuilderByUser($userId)
135 {
136 return $this->createQueryBuilder('t')
137 ->leftJoin('t.entries', 'e')
138 ->where('e.user = :userId')->setParameter('userId', $userId)
139 ->groupBy('t.id')
140 ->orderBy('t.slug');
141 }
121} 142}
diff --git a/src/Wallabag/CoreBundle/Resources/config/services.yml b/src/Wallabag/CoreBundle/Resources/config/services.yml
index 85306276..3f3d4de7 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"
@@ -83,8 +82,10 @@ services:
83 - ["addSubscriber", ["@bd_guzzle_site_authenticator.authenticator_subscriber"]] 82 - ["addSubscriber", ["@bd_guzzle_site_authenticator.authenticator_subscriber"]]
84 83
85 wallabag_core.guzzle.cookie_jar: 84 wallabag_core.guzzle.cookie_jar:
86 class: GuzzleHttp\Cookie\FileCookieJar 85 class: Wallabag\CoreBundle\Helper\FileCookieJar
87 arguments: ["%kernel.cache_dir%/cookiejar.json"] 86 arguments:
87 - "@logger"
88 - "%kernel.cache_dir%/cookiejar.json"
88 89
89 wallabag_core.content_proxy: 90 wallabag_core.content_proxy:
90 class: Wallabag\CoreBundle\Helper\ContentProxy 91 class: Wallabag\CoreBundle\Helper\ContentProxy
@@ -181,6 +182,7 @@ services:
181 182
182 wallabag_core.exception_controller: 183 wallabag_core.exception_controller:
183 class: Wallabag\CoreBundle\Controller\ExceptionController 184 class: Wallabag\CoreBundle\Controller\ExceptionController
185 public: true
184 arguments: 186 arguments:
185 - '@twig' 187 - '@twig'
186 - '%kernel.debug%' 188 - '%kernel.debug%'
@@ -211,10 +213,38 @@ services:
211 - "@logger" 213 - "@logger"
212 214
213 wallabag_core.entry.download_images.client: 215 wallabag_core.entry.download_images.client:
214 class: GuzzleHttp\Client 216 alias: 'httplug.client.wallabag_core.entry.download_images'
215 217
216 wallabag_core.helper.crypto_proxy: 218 wallabag_core.helper.crypto_proxy:
217 class: Wallabag\CoreBundle\Helper\CryptoProxy 219 class: Wallabag\CoreBundle\Helper\CryptoProxy
218 arguments: 220 arguments:
219 - "%wallabag_core.site_credentials.encryption_key_path%" 221 - "%wallabag_core.site_credentials.encryption_key_path%"
220 - "@logger" 222 - "@logger"
223
224 wallabag_core.command.clean_duplicates:
225 class: Wallabag\CoreBundle\Command\CleanDuplicatesCommand
226 tags: ['console.command']
227
228 wallabag_core.command.export:
229 class: Wallabag\CoreBundle\Command\ExportCommand
230 tags: ['console.command']
231
232 wallabag_core.command.install:
233 class: Wallabag\CoreBundle\Command\InstallCommand
234 tags: ['console.command']
235
236 wallabag_core.command.list_user:
237 class: Wallabag\CoreBundle\Command\ListUserCommand
238 tags: ['console.command']
239
240 wallabag_core.command.reload_entry:
241 class: Wallabag\CoreBundle\Command\ReloadEntryCommand
242 tags: ['console.command']
243
244 wallabag_core.command.show_user:
245 class: Wallabag\CoreBundle\Command\ShowUserCommand
246 tags: ['console.command']
247
248 wallabag_core.command.tag_all:
249 class: Wallabag\CoreBundle\Command\TagAllCommand
250 tags: ['console.command']
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.da.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.da.yml
index e3ff21f1..47de066f 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.da.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.da.yml
@@ -33,10 +33,12 @@ menu:
33 back_to_unread: 'Tilbage til de ulæste artikler' 33 back_to_unread: 'Tilbage til de ulæste artikler'
34 # users_management: 'Users management' 34 # users_management: 'Users management'
35 # site_credentials: 'Site credentials' 35 # site_credentials: 'Site credentials'
36 # quickstart: "Quickstart"
36 top: 37 top:
37 add_new_entry: 'Tilføj ny artikel' 38 add_new_entry: 'Tilføj ny artikel'
38 search: 'Søg' 39 search: 'Søg'
39 filter_entries: 'Filtrer artikler' 40 filter_entries: 'Filtrer artikler'
41 # random_entry: Jump to a random entry from that list
40 # export: 'Export' 42 # export: 'Export'
41 search_form: 43 search_form:
42 input_label: 'Indtast søgning' 44 input_label: 'Indtast søgning'
@@ -53,11 +55,12 @@ config:
53 page_title: 'Opsætning' 55 page_title: 'Opsætning'
54 tab_menu: 56 tab_menu:
55 settings: 'Indstillinger' 57 settings: 'Indstillinger'
56 rss: 'RSS' 58 feed: 'RSS'
57 user_info: 'Brugeroplysninger' 59 user_info: 'Brugeroplysninger'
58 password: 'Adgangskode' 60 password: 'Adgangskode'
59 # rules: 'Tagging rules' 61 # rules: 'Tagging rules'
60 new_user: 'Tilføj bruger' 62 new_user: 'Tilføj bruger'
63 # reset: 'Reset area'
61 form: 64 form:
62 save: 'Gem' 65 save: 'Gem'
63 form_settings: 66 form_settings:
@@ -65,12 +68,8 @@ config:
65 items_per_page_label: 'Poster pr. side' 68 items_per_page_label: 'Poster pr. side'
66 language_label: 'Sprog' 69 language_label: 'Sprog'
67 reading_speed: 70 reading_speed:
68 # label: 'Reading speed' 71 # label: 'Reading speed (words per minute)'
69 # help_message: 'You can use online tools to estimate your reading speed:' 72 # help_message: 'You can use online tools to estimate your reading speed:'
70 # 100_word: 'I read ~100 words per minute'
71 # 200_word: 'I read ~200 words per minute'
72 # 300_word: 'I read ~300 words per minute'
73 # 400_word: 'I read ~400 words per minute'
74 action_mark_as_read: 73 action_mark_as_read:
75 # label: 'Where do you want to be redirected to after marking an article as read?' 74 # label: 'Where do you want to be redirected to after marking an article as read?'
76 # redirect_homepage: 'To the homepage' 75 # redirect_homepage: 'To the homepage'
@@ -83,25 +82,35 @@ 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." 82 # 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." 83 # 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." 84 # help_pocket_consumer_key: "Required for Pocket import. You can create it in your Pocket account."
86 form_rss: 85 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.' 86 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' 87 token_label: 'RSS-Token'
89 no_token: 'Intet token' 88 no_token: 'Intet token'
90 token_create: 'Opret token' 89 token_create: 'Opret token'
91 token_reset: 'Nulstil token' 90 token_reset: 'Nulstil token'
92 rss_links: 'RSS-Links' 91 # token_revoke: 'Revoke the token'
93 rss_link: 92 feed_links: 'RSS-Links'
93 feed_link:
94 unread: 'Ulæst' 94 unread: 'Ulæst'
95 starred: 'Favoritter' 95 starred: 'Favoritter'
96 archive: 'Arkiv' 96 archive: 'Arkiv'
97 # all: 'All' 97 # all: 'All'
98 # rss_limit: 'Number of items in the feed' 98 # feed_limit: 'Number of items in the feed'
99 form_user: 99 form_user:
100 # two_factor_description: "Enabling two factor authentication means you'll receive an email with a code on every new untrusted connexion" 100 # two_factor_description: "Enabling two factor authentication means you'll receive an email with a code 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 # login_label: 'Login (can not be changed)'
101 name_label: 'Navn' 102 name_label: 'Navn'
102 email_label: 'Emailadresse' 103 email_label: 'Emailadresse'
103 # twoFactorAuthentication_label: 'Two factor authentication' 104 two_factor:
104 # help_twoFactorAuthentication: "If you enable 2FA, each time you want to login to wallabag, you'll receive a code by email." 105 # emailTwoFactor_label: 'Using email (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.
@@ -127,6 +136,15 @@ config:
127 # edit_rule_label: 'edit' 136 # edit_rule_label: 'edit'
128 # rule_label: 'Rule' 137 # rule_label: 'Rule'
129 # tags_label: 'Tags' 138 # tags_label: 'Tags'
139 # card:
140 # new_tagging_rule: Create a tagging rule
141 # import_tagging_rules: Import tagging rules
142 # import_tagging_rules_detail: You have to select the JSON file you previously exported.
143 # export_tagging_rules: Export tagging rules
144 # export_tagging_rules_detail: This will download a JSON file that you can use to import tagging rules elsewhere or to backup them.
145 # file_label: JSON file
146 # import_submit: Import
147 # export: Export
130 # faq: 148 # faq:
131 # title: 'FAQ' 149 # title: 'FAQ'
132 # tagging_rules_definition_title: 'What does « tagging rules » mean?' 150 # tagging_rules_definition_title: 'What does « tagging rules » mean?'
@@ -159,6 +177,15 @@ config:
159 # and: 'One rule AND another' 177 # 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>' 178 # 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>' 179 # notmatches: 'Tests that a <i>subject</i> doesn''t match match a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
180 otp:
181 # page_title: Two-factor authentication
182 # app:
183 # 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.
184 # two_factor_code_description_2: 'You can scan that QR Code with your app:'
185 # 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:'
186 # two_factor_code_description_4: 'Test an OTP code from your configured app:'
187 # cancel: Cancel
188 # enable: Enable
162 189
163entry: 190entry:
164 # default_title: 'Title of the entry' 191 # default_title: 'Title of the entry'
@@ -237,7 +264,7 @@ entry:
237 # provided_by: 'Provided by' 264 # provided_by: 'Provided by'
238 new: 265 new:
239 page_title: 'Gem ny artikel' 266 page_title: 'Gem ny artikel'
240 placeholder: 'http://website.com' 267 placeholder: 'https://website.dk'
241 form_new: 268 form_new:
242 url_label: Url 269 url_label: Url
243 search: 270 search:
@@ -253,6 +280,11 @@ entry:
253 confirm: 280 confirm:
254 # delete: "Are you sure you want to remove that article?" 281 # delete: "Are you sure you want to remove that article?"
255 # delete_tag: "Are you sure you want to remove that tag from that article?" 282 # delete_tag: "Are you sure you want to remove that tag from that article?"
283 metadata:
284 # reading_time: "Estimated reading time"
285 # reading_time_minutes_short: "%readingTime% min"
286 # address: "Address"
287 # added_on: "Added on"
256 288
257about: 289about:
258 page_title: 'Om' 290 page_title: 'Om'
@@ -348,7 +380,7 @@ quickstart:
348 # title: 'Configure the application' 380 # title: 'Configure the application'
349 # description: 'In order to have an application which suits you, have a look into the configuration of wallabag.' 381 # description: 'In order to have an application which suits you, have a look into the configuration of wallabag.'
350 # language: 'Change language and design' 382 # language: 'Change language and design'
351 # rss: 'Enable RSS feeds' 383 # feed: 'Enable RSS feeds'
352 # tagging_rules: 'Write rules to automatically tag your articles' 384 # tagging_rules: 'Write rules to automatically tag your articles'
353 # admin: 385 # admin:
354 # title: 'Administration' 386 # title: 'Administration'
@@ -396,12 +428,16 @@ tag:
396 list: 428 list:
397 # number_on_the_page: '{0} There is no tag.|{1} There is one tag.|]1,Inf[ There are %count% tags.' 429 # number_on_the_page: '{0} There is no tag.|{1} There is one tag.|]1,Inf[ There are %count% tags.'
398 # see_untagged_entries: 'See untagged entries' 430 # see_untagged_entries: 'See untagged entries'
431 # no_untagged_entries: 'There are no untagged entries.'
399 new: 432 new:
400 # add: 'Add' 433 # add: 'Add'
401 # placeholder: 'You can add several tags, separated by a comma.' 434 # placeholder: 'You can add several tags, separated by a comma.'
435 rename:
436 # placeholder: 'You can update tag name.'
402 437
403# export: 438# export:
404# 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>' 439# 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>'
440# unknown: 'Unknown'
405 441
406import: 442import:
407 # page_title: 'Import' 443 # page_title: 'Import'
@@ -429,6 +465,9 @@ import:
429 # wallabag_v2: 465 # wallabag_v2:
430 # page_title: 'Import > Wallabag v2' 466 # page_title: 'Import > Wallabag v2'
431 # description: 'This importer will import all your wallabag v2 articles. Go to All articles, then, on the export sidebar, click on "JSON". You will have a "All articles.json" file.' 467 # description: 'This importer will import all your wallabag v2 articles. Go to All articles, then, on the export sidebar, click on "JSON". You will have a "All articles.json" file.'
468 # elcurator:
469 # page_title: 'Import > elCurator'
470 # description: 'This importer will import all your elCurator articles. Go to your preferences in your elCurator account and then, export your content. You will have a JSON file.'
432 # readability: 471 # readability:
433 # page_title: 'Import > Readability' 472 # page_title: 'Import > Readability'
434 # description: 'This importer will import all your Readability articles. On the tools (https://www.readability.com/tools/) page, click on "Export your data" in the "Data Export" section. You will received an email to download a json (which does not end with .json in fact).' 473 # description: 'This importer will import all your Readability articles. On the tools (https://www.readability.com/tools/) page, click on "Export your data" in the "Data Export" section. You will received an email to download a json (which does not end with .json in fact).'
@@ -482,6 +521,7 @@ developer:
482 # redirect_uris_label: 'Redirect URIs' 521 # redirect_uris_label: 'Redirect URIs'
483 # save_label: 'Create a new client' 522 # save_label: 'Create a new client'
484 # action_back: 'Back' 523 # action_back: 'Back'
524 # copy_to_clipboard: Copy
485 # client_parameter: 525 # client_parameter:
486 # page_title: 'API clients management > Client parameters' 526 # page_title: 'API clients management > Client parameters'
487 # page_description: 'Here are your client parameters.' 527 # page_description: 'Here are your client parameters.'
@@ -523,7 +563,8 @@ user:
523 email_label: 'Emailadresse' 563 email_label: 'Emailadresse'
524 # enabled_label: 'Enabled' 564 # enabled_label: 'Enabled'
525 # last_login_label: 'Last login' 565 # last_login_label: 'Last login'
526 # twofactor_label: Two factor authentication 566 # twofactor_email_label: Two factor authentication by email
567 # twofactor_google_label: Two factor authentication by OTP app
527 # save: Save 568 # save: Save
528 # delete: Delete 569 # delete: Delete
529 # delete_confirm: Are you sure? 570 # delete_confirm: Are you sure?
@@ -544,7 +585,7 @@ site_credential:
544 # create_new_one: Create a new credential 585 # create_new_one: Create a new credential
545 # form: 586 # form:
546 # username_label: 'Username' 587 # username_label: 'Username'
547 # host_label: 'Host' 588 # host_label: 'Host (subdomain.example.org, .example.org, etc.)'
548 # password_label: 'Password' 589 # password_label: 'Password'
549 # save: Save 590 # save: Save
550 # delete: Delete 591 # delete: Delete
@@ -561,14 +602,18 @@ flashes:
561 password_updated: 'Adgangskode opdateret' 602 password_updated: 'Adgangskode opdateret'
562 # password_not_updated_demo: "In demonstration mode, you can't change password for this user." 603 # password_not_updated_demo: "In demonstration mode, you can't change password for this user."
563 user_updated: 'Oplysninger opdateret' 604 user_updated: 'Oplysninger opdateret'
564 rss_updated: 'RSS-oplysninger opdateret' 605 feed_updated: 'RSS-oplysninger opdateret'
565 # tagging_rules_updated: 'Tagging rules updated' 606 # tagging_rules_updated: 'Tagging rules updated'
566 # tagging_rules_deleted: 'Tagging rule deleted' 607 # tagging_rules_deleted: 'Tagging rule deleted'
567 # rss_token_updated: 'RSS token updated' 608 # feed_token_updated: 'RSS token updated'
609 # feed_token_revoked: 'RSS token revoked'
568 # annotations_reset: Annotations reset 610 # annotations_reset: Annotations reset
569 # tags_reset: Tags reset 611 # tags_reset: Tags reset
570 # entries_reset: Entries reset 612 # entries_reset: Entries reset
571 # archived_reset: Archived entries deleted 613 # archived_reset: Archived entries deleted
614 # otp_enabled: Two-factor authentication enabled
615 # tagging_rules_imported: Tagging rules imported
616 # tagging_rules_not_imported: Error while importing tagging rules
572 entry: 617 entry:
573 notice: 618 notice:
574 # entry_already_saved: 'Entry already saved on %date%' 619 # entry_already_saved: 'Entry already saved on %date%'
@@ -582,9 +627,11 @@ flashes:
582 entry_starred: 'Artikel markeret som favorit' 627 entry_starred: 'Artikel markeret som favorit'
583 entry_unstarred: 'Artikel ikke længere markeret som favorit' 628 entry_unstarred: 'Artikel ikke længere markeret som favorit'
584 entry_deleted: 'Artikel slettet' 629 entry_deleted: 'Artikel slettet'
630 # no_random_entry: 'No article with these criterias was found'
585 tag: 631 tag:
586 notice: 632 notice:
587 # tag_added: 'Tag added' 633 # tag_added: 'Tag added'
634 # tag_renamed: 'Tag renamed'
588 import: 635 import:
589 notice: 636 notice:
590 # failed: 'Import failed, please try again.' 637 # 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 c297ffb5..50e67d47 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.de.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.de.yml
@@ -33,10 +33,12 @@ menu:
33 back_to_unread: 'Zurück zu ungelesenen Artikeln' 33 back_to_unread: 'Zurück zu ungelesenen Artikeln'
34 users_management: 'Benutzerverwaltung' 34 users_management: 'Benutzerverwaltung'
35 site_credentials: 'Zugangsdaten' 35 site_credentials: 'Zugangsdaten'
36 quickstart: "Schnelleinstieg"
36 top: 37 top:
37 add_new_entry: 'Neuen Artikel hinzufügen' 38 add_new_entry: 'Neuen Artikel hinzufügen'
38 search: 'Suche' 39 search: 'Suche'
39 filter_entries: 'Artikel filtern' 40 filter_entries: 'Artikel filtern'
41 # random_entry: Jump to a random entry from that list
40 export: 'Exportieren' 42 export: 'Exportieren'
41 search_form: 43 search_form:
42 input_label: 'Suchbegriff hier eingeben' 44 input_label: 'Suchbegriff hier eingeben'
@@ -53,11 +55,12 @@ config:
53 page_title: 'Einstellungen' 55 page_title: 'Einstellungen'
54 tab_menu: 56 tab_menu:
55 settings: 'Einstellungen' 57 settings: 'Einstellungen'
56 rss: 'RSS' 58 feed: 'RSS'
57 user_info: 'Benutzerinformation' 59 user_info: 'Benutzerinformation'
58 password: 'Kennwort' 60 password: 'Kennwort'
59 rules: 'Tagging-Regeln' 61 rules: 'Tagging-Regeln'
60 new_user: 'Benutzer hinzufügen' 62 new_user: 'Benutzer hinzufügen'
63 reset: 'Zurücksetzen'
61 form: 64 form:
62 save: 'Speichern' 65 save: 'Speichern'
63 form_settings: 66 form_settings:
@@ -65,12 +68,8 @@ config:
65 items_per_page_label: 'Einträge pro Seite' 68 items_per_page_label: 'Einträge pro Seite'
66 language_label: 'Sprache' 69 language_label: 'Sprache'
67 reading_speed: 70 reading_speed:
68 label: 'Lesegeschwindigkeit' 71 label: 'Lesegeschwindigkeit (Wörter pro Minute)'
69 help_message: 'Du kannst Online-Tools nutzen, um deine Lesegeschwindigkeit herauszufinden.' 72 help_message: 'Du kannst Online-Tools nutzen, um deine Lesegeschwindigkeit herauszufinden.'
70 100_word: 'Ich lese ~100 Wörter pro Minute'
71 200_word: 'Ich lese ~200 Wörter pro Minute'
72 300_word: 'Ich lese ~300 Wörter pro Minute'
73 400_word: 'Ich lese ~400 Wörter pro Minute'
74 action_mark_as_read: 73 action_mark_as_read:
75 label: 'Wohin soll nach dem Gelesenmarkieren eines Artikels weitergeleitet werden?' 74 label: 'Wohin soll nach dem Gelesenmarkieren eines Artikels weitergeleitet werden?'
76 redirect_homepage: 'Zur Homepage' 75 redirect_homepage: 'Zur Homepage'
@@ -83,25 +82,35 @@ 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." 82 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." 83 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." 84 help_pocket_consumer_key: "Nötig für den Pocket-Import. Du kannst ihn in deinem Pocket account einrichten."
86 form_rss: 85 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.' 86 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' 87 token_label: 'RSS-Token'
89 no_token: 'Kein Token' 88 no_token: 'Kein Token'
90 token_create: 'Token erstellen' 89 token_create: 'Token erstellen'
91 token_reset: 'Token zurücksetzen' 90 token_reset: 'Token zurücksetzen'
92 rss_links: 'RSS-Links' 91 # token_revoke: 'Revoke the token'
93 rss_link: 92 feed_links: 'RSS-Links'
93 feed_link:
94 unread: 'Ungelesene' 94 unread: 'Ungelesene'
95 starred: 'Favoriten' 95 starred: 'Favoriten'
96 archive: 'Archivierte' 96 archive: 'Archivierte'
97 all: 'Alle' 97 all: 'Alle'
98 rss_limit: 'Anzahl der Einträge pro Feed' 98 feed_limit: 'Anzahl der Einträge pro Feed'
99 form_user: 99 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" 100 # 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 # login_label: 'Login (can not be changed)'
101 name_label: 'Name' 102 name_label: 'Name'
102 email_label: 'E-Mail-Adresse' 103 email_label: 'E-Mail-Adresse'
103 twoFactorAuthentication_label: 'Zwei-Faktor-Authentifizierung' 104 two_factor:
104 help_twoFactorAuthentication: "Wenn du 2FA aktivierst, wirst du bei jedem Login einen Code per E-Mail bekommen." 105 # emailTwoFactor_label: 'Using email (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: 'Lösche mein Konto (a.k.a Gefahrenzone)' 115 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.' 116 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.'
@@ -127,6 +136,15 @@ config:
127 edit_rule_label: 'bearbeiten' 136 edit_rule_label: 'bearbeiten'
128 rule_label: 'Regel' 137 rule_label: 'Regel'
129 tags_label: 'Tags' 138 tags_label: 'Tags'
139 # card:
140 # new_tagging_rule: Create a tagging rule
141 # import_tagging_rules: Import tagging rules
142 # import_tagging_rules_detail: You have to select the JSON file you previously exported.
143 # export_tagging_rules: Export tagging rules
144 # export_tagging_rules_detail: This will download a JSON file that you can use to import tagging rules elsewhere or to backup them.
145 # file_label: JSON file
146 # import_submit: Import
147 # export: Export
130 faq: 148 faq:
131 title: 'FAQ' 149 title: 'FAQ'
132 tagging_rules_definition_title: 'Was bedeuten die "Tagging-Regeln"?' 150 tagging_rules_definition_title: 'Was bedeuten die "Tagging-Regeln"?'
@@ -159,6 +177,15 @@ config:
159 and: 'Eine Regel UND eine andere' 177 and: 'Eine Regel UND eine andere'
160 matches: 'Testet, ob eine <i>Variable</i> auf eine <i>Suche</i> zutrifft (Groß- und Kleinschreibung wird nicht berücksichtigt).<br />Beispiel: <code>title matches "Fußball"</code>' 178 matches: 'Testet, ob eine <i>Variable</i> auf eine <i>Suche</i> zutrifft (Groß- und Kleinschreibung wird nicht berücksichtigt).<br />Beispiel: <code>title matches "Fußball"</code>'
161 notmatches: 'Testet, ob ein <i>Titel</i> nicht auf eine <i>Suche</i> zutrifft (Groß- und Kleinschreibung wird nicht berücksichtigt).<br />Beispiel: <code>title notmatches "Fußball"</code>' 179 notmatches: 'Testet, ob ein <i>Titel</i> nicht auf eine <i>Suche</i> zutrifft (Groß- und Kleinschreibung wird nicht berücksichtigt).<br />Beispiel: <code>title notmatches "Fußball"</code>'
180 otp:
181 # page_title: Two-factor authentication
182 # app:
183 # 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.
184 # two_factor_code_description_2: 'You can scan that QR Code with your app:'
185 # 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:'
186 # two_factor_code_description_4: 'Test an OTP code from your configured app:'
187 # cancel: Cancel
188 # enable: Enable
162 189
163entry: 190entry:
164 default_title: 'Titel des Eintrags' 191 default_title: 'Titel des Eintrags'
@@ -253,6 +280,11 @@ entry:
253 confirm: 280 confirm:
254 delete: 'Bist du sicher, dass du diesen Artikel löschen möchtest?' 281 delete: 'Bist du sicher, dass du diesen Artikel löschen möchtest?'
255 delete_tag: 'Bist du sicher, dass du diesen Tag vom Artikel entfernen möchtest?' 282 delete_tag: 'Bist du sicher, dass du diesen Tag vom Artikel entfernen möchtest?'
283 metadata:
284 # reading_time: "Estimated reading time"
285 # reading_time_minutes_short: "%readingTime% min"
286 # address: "Address"
287 # added_on: "Added on"
256 288
257about: 289about:
258 page_title: 'Über' 290 page_title: 'Über'
@@ -348,7 +380,7 @@ quickstart:
348 title: 'Anwendung konfigurieren' 380 title: 'Anwendung konfigurieren'
349 description: 'Um die Applikation für dich anzupassen, schau in die Konfiguration von wallabag.' 381 description: 'Um die Applikation für dich anzupassen, schau in die Konfiguration von wallabag.'
350 language: 'Sprache und Design ändern' 382 language: 'Sprache und Design ändern'
351 rss: 'RSS-Feeds aktivieren' 383 feed: 'RSS-Feeds aktivieren'
352 tagging_rules: 'Schreibe Regeln, um deine Beiträge automatisch zu taggen (verschlagworten)' 384 tagging_rules: 'Schreibe Regeln, um deine Beiträge automatisch zu taggen (verschlagworten)'
353 admin: 385 admin:
354 title: 'Administration' 386 title: 'Administration'
@@ -396,12 +428,16 @@ tag:
396 list: 428 list:
397 number_on_the_page: '{0} Es gibt keine Tags.|{1} Es gibt einen Tag.|]1,Inf[ Es gibt %count% Tags.' 429 number_on_the_page: '{0} Es gibt keine Tags.|{1} Es gibt einen Tag.|]1,Inf[ Es gibt %count% Tags.'
398 see_untagged_entries: 'Zeige nicht getaggte Einträge' 430 see_untagged_entries: 'Zeige nicht getaggte Einträge'
431 # no_untagged_entries: 'There are no untagged entries.'
399 new: 432 new:
400 add: 'Hinzufügen' 433 add: 'Hinzufügen'
401 placeholder: 'Du kannst verschiedene Tags, getrennt von einem Komma, hinzufügen.' 434 placeholder: 'Du kannst verschiedene Tags, getrennt von einem Komma, hinzufügen.'
435 rename:
436 # placeholder: 'You can update tag name.'
402 437
403export: 438export:
404 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>' 439 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>'
440 # unknown: 'Unknown'
405 441
406import: 442import:
407 page_title: 'Importieren' 443 page_title: 'Importieren'
@@ -429,6 +465,9 @@ import:
429 wallabag_v2: 465 wallabag_v2:
430 page_title: 'Aus wallabag v2 importieren' 466 page_title: 'Aus wallabag v2 importieren'
431 description: 'Dieser Import wird all deine Artikel aus wallabag v2 importieren. Gehe auf "Alle Artikel" und dann, in der Exportieren-Seitenleiste auf "JSON". Dabei erhältst du eine "All articles.json"-Datei.' 467 description: 'Dieser Import wird all deine Artikel aus wallabag v2 importieren. Gehe auf "Alle Artikel" und dann, in der Exportieren-Seitenleiste auf "JSON". Dabei erhältst du eine "All articles.json"-Datei.'
468 # elcurator:
469 # page_title: 'Import > elCurator'
470 # description: 'This importer will import all your elCurator articles. Go to your preferences in your elCurator account and then, export your content. You will have a JSON file.'
432 readability: 471 readability:
433 page_title: 'Aus Readability importieren' 472 page_title: 'Aus Readability importieren'
434 description: 'Dieser Import wird all deine Artikel aus Readability importieren. Auf der Tools Seite (https://www.readability.com/tools/) klickst du auf "Exportiere deine Daten" in dem Abschnitt "Datenexport". Du wirst eine E-Mail mit einem Downloadlink zu einer json Datei, die aber nicht auf .json endet, erhalten' 473 description: 'Dieser Import wird all deine Artikel aus Readability importieren. Auf der Tools Seite (https://www.readability.com/tools/) klickst du auf "Exportiere deine Daten" in dem Abschnitt "Datenexport". Du wirst eine E-Mail mit einem Downloadlink zu einer json Datei, die aber nicht auf .json endet, erhalten'
@@ -482,6 +521,7 @@ developer:
482 redirect_uris_label: 'Weiterleitungs-URIs' 521 redirect_uris_label: 'Weiterleitungs-URIs'
483 save_label: 'Neuen Client erstellen' 522 save_label: 'Neuen Client erstellen'
484 action_back: 'Zurück' 523 action_back: 'Zurück'
524 # copy_to_clipboard: Copy
485 client_parameter: 525 client_parameter:
486 page_title: 'API-Client-Verwaltung > Client-Parameter' 526 page_title: 'API-Client-Verwaltung > Client-Parameter'
487 page_description: 'Dies sind deine Client-Parameter.' 527 page_description: 'Dies sind deine Client-Parameter.'
@@ -523,7 +563,8 @@ user:
523 email_label: 'E-Mail-Adresse' 563 email_label: 'E-Mail-Adresse'
524 enabled_label: 'Aktiviert' 564 enabled_label: 'Aktiviert'
525 last_login_label: 'Letzter Login' 565 last_login_label: 'Letzter Login'
526 twofactor_label: 'Zwei-Faktor-Authentifizierung' 566 # twofactor_email_label: Two factor authentication by email
567 # twofactor_google_label: Two factor authentication by OTP app
527 save: 'Speichern' 568 save: 'Speichern'
528 delete: 'Löschen' 569 delete: 'Löschen'
529 delete_confirm: 'Bist du sicher?' 570 delete_confirm: 'Bist du sicher?'
@@ -544,7 +585,7 @@ site_credential:
544 create_new_one: 'Einen neuen Seitenzugang anlegen' 585 create_new_one: 'Einen neuen Seitenzugang anlegen'
545 form: 586 form:
546 username_label: 'Benutzername' 587 username_label: 'Benutzername'
547 host_label: 'Host' 588 host_label: 'Host (subdomain.example.org, .example.org, etc.)'
548 password_label: 'Passwort' 589 password_label: 'Passwort'
549 save: 'Speichern' 590 save: 'Speichern'
550 delete: 'Löschen' 591 delete: 'Löschen'
@@ -561,14 +602,18 @@ flashes:
561 password_updated: 'Kennwort aktualisiert' 602 password_updated: 'Kennwort aktualisiert'
562 password_not_updated_demo: 'Im Testmodus kannst du das Kennwort nicht ändern.' 603 password_not_updated_demo: 'Im Testmodus kannst du das Kennwort nicht ändern.'
563 user_updated: 'Information aktualisiert' 604 user_updated: 'Information aktualisiert'
564 rss_updated: 'RSS-Informationen aktualisiert' 605 feed_updated: 'RSS-Informationen aktualisiert'
565 tagging_rules_updated: 'Tagging-Regeln aktualisiert' 606 tagging_rules_updated: 'Tagging-Regeln aktualisiert'
566 tagging_rules_deleted: 'Tagging-Regel gelöscht' 607 tagging_rules_deleted: 'Tagging-Regel gelöscht'
567 rss_token_updated: 'RSS-Token aktualisiert' 608 feed_token_updated: 'RSS-Token aktualisiert'
568 annotations_reset: 'Anmerkungen zurücksetzen' 609 # feed_token_revoked: 'RSS token revoked'
569 tags_reset: 'Tags zurücksetzen' 610 annotations_reset: Anmerkungen zurücksetzen
570 entries_reset: 'Einträge zurücksetzen' 611 tags_reset: Tags zurücksetzen
571 archived_reset: 'Archiverte Einträge zurücksetzen' 612 entries_reset: Einträge zurücksetzen
613 archived_reset: Archiverte Einträge zurücksetzen
614 # otp_enabled: Two-factor authentication enabled
615 # tagging_rules_imported: Tagging rules imported
616 # tagging_rules_not_imported: Error while importing tagging rules
572 entry: 617 entry:
573 notice: 618 notice:
574 entry_already_saved: 'Eintrag bereits am %date% gespeichert' 619 entry_already_saved: 'Eintrag bereits am %date% gespeichert'
@@ -582,9 +627,11 @@ flashes:
582 entry_starred: 'Eintrag favorisiert' 627 entry_starred: 'Eintrag favorisiert'
583 entry_unstarred: 'Eintrag defavorisiert' 628 entry_unstarred: 'Eintrag defavorisiert'
584 entry_deleted: 'Eintrag gelöscht' 629 entry_deleted: 'Eintrag gelöscht'
630 # no_random_entry: 'No article with these criterias was found'
585 tag: 631 tag:
586 notice: 632 notice:
587 tag_added: 'Tag hinzugefügt' 633 tag_added: 'Tag hinzugefügt'
634 #tag_renamed: 'Tag renamed'
588 import: 635 import:
589 notice: 636 notice:
590 failed: 'Import fehlgeschlagen, bitte erneut probieren.' 637 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 169ae728..27585f77 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.en.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.en.yml
@@ -5,7 +5,7 @@ security:
5 forgot_password: 'Forgot your password?' 5 forgot_password: 'Forgot your password?'
6 submit: 'Login' 6 submit: 'Login'
7 register: 'Register' 7 register: 'Register'
8 username: 'Username' 8 username: 'Login'
9 password: 'Password' 9 password: 'Password'
10 cancel: 'Cancel' 10 cancel: 'Cancel'
11 resetting: 11 resetting:
@@ -33,10 +33,12 @@ menu:
33 back_to_unread: 'Back to unread articles' 33 back_to_unread: 'Back to unread articles'
34 users_management: 'Users management' 34 users_management: 'Users management'
35 site_credentials: 'Site credentials' 35 site_credentials: 'Site credentials'
36 quickstart: "Quickstart"
36 top: 37 top:
37 add_new_entry: 'Add a new entry' 38 add_new_entry: 'Add a new entry'
38 search: 'Search' 39 search: 'Search'
39 filter_entries: 'Filter entries' 40 filter_entries: 'Filter entries'
41 random_entry: Jump to a random entry from that list
40 export: 'Export' 42 export: 'Export'
41 search_form: 43 search_form:
42 input_label: 'Enter your search here' 44 input_label: 'Enter your search here'
@@ -53,11 +55,12 @@ config:
53 page_title: 'Config' 55 page_title: 'Config'
54 tab_menu: 56 tab_menu:
55 settings: 'Settings' 57 settings: 'Settings'
56 rss: 'RSS' 58 feed: 'Feeds'
57 user_info: 'User information' 59 user_info: 'User information'
58 password: 'Password' 60 password: 'Password'
59 rules: 'Tagging rules' 61 rules: 'Tagging rules'
60 new_user: 'Add a user' 62 new_user: 'Add a user'
63 reset: 'Reset area'
61 form: 64 form:
62 save: 'Save' 65 save: 'Save'
63 form_settings: 66 form_settings:
@@ -65,12 +68,8 @@ config:
65 items_per_page_label: 'Items per page' 68 items_per_page_label: 'Items per page'
66 language_label: 'Language' 69 language_label: 'Language'
67 reading_speed: 70 reading_speed:
68 label: 'Reading speed' 71 label: 'Reading speed (words per minute)'
69 help_message: 'You can use online tools to estimate your reading speed:' 72 help_message: 'You can use online tools to estimate your reading speed:'
70 100_word: 'I read ~100 words per minute'
71 200_word: 'I read ~200 words per minute'
72 300_word: 'I read ~300 words per minute'
73 400_word: 'I read ~400 words per minute'
74 action_mark_as_read: 73 action_mark_as_read:
75 label: 'What to do after removing, starring or marking as read an article?' 74 label: 'What to do after removing, starring or marking as read an article?'
76 redirect_homepage: 'Go to the homepage' 75 redirect_homepage: 'Go to the homepage'
@@ -83,25 +82,35 @@ 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." 82 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." 83 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." 84 help_pocket_consumer_key: "Required for Pocket import. You can create it in your Pocket account."
86 form_rss: 85 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.' 86 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' 87 token_label: 'Feed token'
89 no_token: 'No token' 88 no_token: 'No token'
90 token_create: 'Create your token' 89 token_create: 'Create your token'
91 token_reset: 'Regenerate your token' 90 token_reset: 'Regenerate your token'
92 rss_links: 'RSS links' 91 token_revoke: 'Revoke the token'
93 rss_link: 92 feed_links: 'Feed links'
93 feed_link:
94 unread: 'Unread' 94 unread: 'Unread'
95 starred: 'Starred' 95 starred: 'Starred'
96 archive: 'Archived' 96 archive: 'Archived'
97 all: 'All' 97 all: 'All'
98 rss_limit: 'Number of items in the feed' 98 feed_limit: 'Number of items in the feed'
99 form_user: 99 form_user:
100 two_factor_description: "Enabling two factor authentication means you'll receive an email with a code on every new untrusted connection." 100 two_factor_description: "Enabling two factor authentication means you'll receive an email with a code 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 login_label: 'Login (can not be changed)'
101 name_label: 'Name' 102 name_label: 'Name'
102 email_label: 'Email' 103 email_label: 'Email'
103 twoFactorAuthentication_label: 'Two factor authentication' 104 two_factor:
104 help_twoFactorAuthentication: "If you enable 2FA, each time you want to login to wallabag, you'll receive a code by email." 105 emailTwoFactor_label: 'Using email (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.
@@ -127,6 +136,15 @@ config:
127 edit_rule_label: 'edit' 136 edit_rule_label: 'edit'
128 rule_label: 'Rule' 137 rule_label: 'Rule'
129 tags_label: 'Tags' 138 tags_label: 'Tags'
139 card:
140 new_tagging_rule: Create a tagging rule
141 import_tagging_rules: Import tagging rules
142 import_tagging_rules_detail: You have to select the JSON file you previously exported.
143 export_tagging_rules: Export tagging rules
144 export_tagging_rules_detail: This will download a JSON file that you can use to import tagging rules elsewhere or to backup them.
145 file_label: JSON file
146 import_submit: Import
147 export: Export
130 faq: 148 faq:
131 title: 'FAQ' 149 title: 'FAQ'
132 tagging_rules_definition_title: 'What does « tagging rules » mean?' 150 tagging_rules_definition_title: 'What does « tagging rules » mean?'
@@ -159,6 +177,15 @@ config:
159 and: 'One rule AND another' 177 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>' 178 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>' 179 notmatches: 'Tests that a <i>subject</i> doesn''t match match a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
180 otp:
181 page_title: Two-factor authentication
182 app:
183 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.
184 two_factor_code_description_2: 'You can scan that QR Code with your app:'
185 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:'
186 two_factor_code_description_4: 'Test an OTP code from your configured app:'
187 cancel: Cancel
188 enable: Enable
162 189
163entry: 190entry:
164 default_title: 'Title of the entry' 191 default_title: 'Title of the entry'
@@ -237,7 +264,7 @@ entry:
237 provided_by: 'Provided by' 264 provided_by: 'Provided by'
238 new: 265 new:
239 page_title: 'Save new entry' 266 page_title: 'Save new entry'
240 placeholder: 'http://website.com' 267 placeholder: 'https://website.com'
241 form_new: 268 form_new:
242 url_label: Url 269 url_label: Url
243 search: 270 search:
@@ -253,6 +280,11 @@ entry:
253 confirm: 280 confirm:
254 delete: "Are you sure you want to remove that article?" 281 delete: "Are you sure you want to remove that article?"
255 delete_tag: "Are you sure you want to remove that tag from that article?" 282 delete_tag: "Are you sure you want to remove that tag from that article?"
283 metadata:
284 reading_time: "Estimated reading time"
285 reading_time_minutes_short: "%readingTime% min"
286 address: "Address"
287 added_on: "Added on"
256 288
257about: 289about:
258 page_title: 'About' 290 page_title: 'About'
@@ -348,7 +380,7 @@ quickstart:
348 title: 'Configure the application' 380 title: 'Configure the application'
349 description: 'In order to have an application which suits you, have a look into the configuration of wallabag.' 381 description: 'In order to have an application which suits you, have a look into the configuration of wallabag.'
350 language: 'Change language and design' 382 language: 'Change language and design'
351 rss: 'Enable RSS feeds' 383 feed: 'Enable feeds'
352 tagging_rules: 'Write rules to automatically tag your articles' 384 tagging_rules: 'Write rules to automatically tag your articles'
353 admin: 385 admin:
354 title: 'Administration' 386 title: 'Administration'
@@ -396,12 +428,16 @@ tag:
396 list: 428 list:
397 number_on_the_page: '{0} There are no tags.|{1} There is one tag.|]1,Inf[ There are %count% tags.' 429 number_on_the_page: '{0} There are no tags.|{1} There is one tag.|]1,Inf[ There are %count% tags.'
398 see_untagged_entries: 'See untagged entries' 430 see_untagged_entries: 'See untagged entries'
431 no_untagged_entries: 'There are no untagged entries.'
399 new: 432 new:
400 add: 'Add' 433 add: 'Add'
401 placeholder: 'You can add several tags, separated by a comma.' 434 placeholder: 'You can add several tags, separated by a comma.'
435 rename:
436 placeholder: 'You can update tag name.'
402 437
403export: 438export:
404 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>' 439 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>'
440 unknown: 'Unknown'
405 441
406import: 442import:
407 page_title: 'Import' 443 page_title: 'Import'
@@ -429,6 +465,9 @@ import:
429 wallabag_v2: 465 wallabag_v2:
430 page_title: 'Import > Wallabag v2' 466 page_title: 'Import > Wallabag v2'
431 description: 'This importer will import all your wallabag v2 articles. Go to All articles, then, on the export sidebar, click on "JSON". You will have a "All articles.json" file.' 467 description: 'This importer will import all your wallabag v2 articles. Go to All articles, then, on the export sidebar, click on "JSON". You will have a "All articles.json" file.'
468 elcurator:
469 page_title: 'Import > elCurator'
470 description: 'This importer will import all your elCurator articles. Go to your preferences in your elCurator account and then, export your content. You will have a JSON file.'
432 readability: 471 readability:
433 page_title: 'Import > Readability' 472 page_title: 'Import > Readability'
434 description: 'This importer will import all your Readability articles. On the tools (https://www.readability.com/tools/) page, click on "Export your data" in the "Data Export" section. You will received an email to download a json (which does not end with .json in fact).' 473 description: 'This importer will import all your Readability articles. On the tools (https://www.readability.com/tools/) page, click on "Export your data" in the "Data Export" section. You will received an email to download a json (which does not end with .json in fact).'
@@ -482,6 +521,7 @@ developer:
482 redirect_uris_label: 'Redirect URIs (optional)' 521 redirect_uris_label: 'Redirect URIs (optional)'
483 save_label: 'Create a new client' 522 save_label: 'Create a new client'
484 action_back: 'Back' 523 action_back: 'Back'
524 copy_to_clipboard: Copy
485 client_parameter: 525 client_parameter:
486 page_title: 'API clients management > Client parameters' 526 page_title: 'API clients management > Client parameters'
487 page_description: 'Here are your client parameters.' 527 page_description: 'Here are your client parameters.'
@@ -515,7 +555,7 @@ user:
515 no: No 555 no: No
516 create_new_one: Create a new user 556 create_new_one: Create a new user
517 form: 557 form:
518 username_label: 'Username' 558 username_label: 'Login'
519 name_label: 'Name' 559 name_label: 'Name'
520 password_label: 'Password' 560 password_label: 'Password'
521 repeat_new_password_label: 'Repeat new password' 561 repeat_new_password_label: 'Repeat new password'
@@ -523,13 +563,14 @@ user:
523 email_label: 'Email' 563 email_label: 'Email'
524 enabled_label: 'Enabled' 564 enabled_label: 'Enabled'
525 last_login_label: 'Last login' 565 last_login_label: 'Last login'
526 twofactor_label: Two factor authentication 566 twofactor_email_label: Two factor authentication by email
567 twofactor_google_label: Two factor authentication by OTP app
527 save: Save 568 save: Save
528 delete: Delete 569 delete: Delete
529 delete_confirm: Are you sure? 570 delete_confirm: Are you sure?
530 back_to_list: Back to list 571 back_to_list: Back to list
531 search: 572 search:
532 placeholder: Filter by username or email 573 placeholder: Filter by login or email
533 574
534site_credential: 575site_credential:
535 page_title: Site credentials management 576 page_title: Site credentials management
@@ -543,8 +584,8 @@ site_credential:
543 no: No 584 no: No
544 create_new_one: Create a new credential 585 create_new_one: Create a new credential
545 form: 586 form:
546 username_label: 'Username' 587 username_label: 'Login'
547 host_label: 'Host' 588 host_label: 'Host (subdomain.example.org, .example.org, etc.)'
548 password_label: 'Password' 589 password_label: 'Password'
549 save: Save 590 save: Save
550 delete: Delete 591 delete: Delete
@@ -561,14 +602,18 @@ flashes:
561 password_updated: 'Password updated' 602 password_updated: 'Password updated'
562 password_not_updated_demo: "In demonstration mode, you can't change password for this user." 603 password_not_updated_demo: "In demonstration mode, you can't change password for this user."
563 user_updated: 'Information updated' 604 user_updated: 'Information updated'
564 rss_updated: 'RSS information updated' 605 feed_updated: 'Feed information updated'
565 tagging_rules_updated: 'Tagging rules updated' 606 tagging_rules_updated: 'Tagging rules updated'
566 tagging_rules_deleted: 'Tagging rule deleted' 607 tagging_rules_deleted: 'Tagging rule deleted'
567 rss_token_updated: 'RSS token updated' 608 feed_token_updated: 'Feed token updated'
609 feed_token_revoked: 'RSS token revoked'
568 annotations_reset: Annotations reset 610 annotations_reset: Annotations reset
569 tags_reset: Tags reset 611 tags_reset: Tags reset
570 entries_reset: Entries reset 612 entries_reset: Entries reset
571 archived_reset: Archived entries deleted 613 archived_reset: Archived entries deleted
614 otp_enabled: Two-factor authentication enabled
615 tagging_rules_imported: Tagging rules imported
616 tagging_rules_not_imported: Error while importing tagging rules
572 entry: 617 entry:
573 notice: 618 notice:
574 entry_already_saved: 'Entry already saved on %date%' 619 entry_already_saved: 'Entry already saved on %date%'
@@ -582,9 +627,11 @@ flashes:
582 entry_starred: 'Entry starred' 627 entry_starred: 'Entry starred'
583 entry_unstarred: 'Entry unstarred' 628 entry_unstarred: 'Entry unstarred'
584 entry_deleted: 'Entry deleted' 629 entry_deleted: 'Entry deleted'
630 no_random_entry: 'No article with these criterias was found'
585 tag: 631 tag:
586 notice: 632 notice:
587 tag_added: 'Tag added' 633 tag_added: 'Tag added'
634 tag_renamed: 'Tag renamed'
588 import: 635 import:
589 notice: 636 notice:
590 failed: 'Import failed, please try again.' 637 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 039a1867..2cf67176 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.es.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.es.yml
@@ -32,11 +32,13 @@ menu:
32 save_link: 'Guardar un enlace' 32 save_link: 'Guardar un enlace'
33 back_to_unread: 'Volver a los artículos sin leer' 33 back_to_unread: 'Volver a los artículos sin leer'
34 users_management: 'Configuración de usuarios' 34 users_management: 'Configuración de usuarios'
35 # site_credentials: 'Site credentials' 35 site_credentials: 'Credenciales del sitio'
36 quickstart: "Inicio rápido"
36 top: 37 top:
37 add_new_entry: 'Añadir un nuevo artículo' 38 add_new_entry: 'Añadir un nuevo artículo'
38 search: 'Buscar' 39 search: 'Buscar'
39 filter_entries: 'Filtrar los artículos' 40 filter_entries: 'Filtrar los artículos'
41 random_entry: 'Ir a un artículo aleatório de esta lista'
40 export: 'Exportar' 42 export: 'Exportar'
41 search_form: 43 search_form:
42 input_label: 'Introduzca su búsqueda aquí' 44 input_label: 'Introduzca su búsqueda aquí'
@@ -45,7 +47,7 @@ footer:
45 wallabag: 47 wallabag:
46 elsewhere: 'Lleva wallabag contigo' 48 elsewhere: 'Lleva wallabag contigo'
47 social: 'Social' 49 social: 'Social'
48 powered_by: 'funciona con' 50 powered_by: 'impulsado por'
49 about: 'Acerca de' 51 about: 'Acerca de'
50 stats: Desde el %user_creation% has leído %nb_archives% artículos. ¡Eso hace unos %per_day% por día! 52 stats: Desde el %user_creation% has leído %nb_archives% artículos. ¡Eso hace unos %per_day% por día!
51 53
@@ -53,11 +55,12 @@ config:
53 page_title: 'Configuración' 55 page_title: 'Configuración'
54 tab_menu: 56 tab_menu:
55 settings: 'Configuración' 57 settings: 'Configuración'
56 rss: 'RSS' 58 feed: 'RSS'
57 user_info: 'Información de usuario' 59 user_info: 'Información de usuario'
58 password: 'Contraseña' 60 password: 'Contraseña'
59 rules: 'Reglas de etiquetado automáticas' 61 rules: 'Reglas de etiquetado automáticas'
60 new_user: 'Añadir un usuario' 62 new_user: 'Añadir un usuario'
63 reset: 'Reiniciar mi cuenta'
61 form: 64 form:
62 save: 'Guardar' 65 save: 'Guardar'
63 form_settings: 66 form_settings:
@@ -65,43 +68,49 @@ config:
65 items_per_page_label: 'Número de artículos por página' 68 items_per_page_label: 'Número de artículos por página'
66 language_label: 'Idioma' 69 language_label: 'Idioma'
67 reading_speed: 70 reading_speed:
68 label: 'Velocidad de lectura' 71 label: 'Velocidad de lectura (palabras por minuto)'
69 help_message: 'Puede utilizar herramientas en línea para calcular su velocidad de lectura:' 72 help_message: 'Puede utilizar herramientas en línea para calcular su velocidad de lectura:'
70 100_word: 'Leo ~100 palabras por minuto'
71 200_word: 'Leo ~200 palabras por minuto'
72 300_word: 'Leo ~300 palabras por minuto'
73 400_word: 'Leo ~400 palabras por minuto'
74 action_mark_as_read: 73 action_mark_as_read:
75 label: '¿Dónde quieres ser redirigido después de marcar un artículo como leído?' 74 label: '¿Dónde quieres ser redirigido después de marcar un artículo como leído?'
76 redirect_homepage: 'A la página de inicio' 75 redirect_homepage: 'A la página de inicio'
77 redirect_current_page: 'A la página actual' 76 redirect_current_page: 'A la página actual'
78 pocket_consumer_key_label: Clave de consumidor para importar contenidos de Pocket 77 pocket_consumer_key_label: Clave de consumidor para importar contenidos de Pocket
79 android_configuration: Configura tu aplicación Android 78 android_configuration: Configura tu aplicación Android
80 # android_instruction: "Touch here to prefill your Android application" 79 android_instruction: "Toca aquí para prellenar tu aplicación Android"
81 help_theme: "wallabag es personalizable. Puedes elegir tu tema preferido aquí." 80 help_theme: "wallabag es personalizable. Puedes elegir tu tema preferido aquí."
82 help_items_per_page: "Puedes cambiar el número de artículos mostrados en cada página." 81 help_items_per_page: "Puedes cambiar el número de artículos mostrados en cada página."
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." 82 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." 83 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." 84 help_pocket_consumer_key: "Requerido para la importación desde Pocket. Puedes crearla en tu cuenta de Pocket."
86 form_rss: 85 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.' 86 description: 'Los feeds RSS de wallabag permiten leer los artículos guardados con tu lector RSS favorito. Primero necesitas generar un token.'
88 token_label: 'Token RSS' 87 token_label: 'Token RSS'
89 no_token: 'Sin token' 88 no_token: 'Sin token'
90 token_create: 'Crear token' 89 token_create: 'Crear token'
91 token_reset: 'Reiniciar token' 90 token_reset: 'Reiniciar token'
92 rss_links: 'URLs de feeds RSS' 91 token_revoke: 'Revocar token'
93 rss_link: 92 feed_links: 'URLs de feeds RSS'
94 unread: 'sin leer' 93 feed_link:
95 starred: 'favoritos' 94 unread: 'Sin leer'
96 archive: 'archivados' 95 starred: 'Favoritos'
97 # all: 'All' 96 archive: 'Archivados'
98 rss_limit: 'Límite de artículos en feed RSS' 97 all: 'Todos'
98 feed_limit: 'Límite de artículos en feed RSS'
99 form_user: 99 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." 100 two_factor_description: "Activar la autenticación de dos pasos significa que recibirás un correo electrónico con un código o que necesitarás usar una aplicación OTP (como Google Authenticator, Authy or FreeOTP) para conseguir un código de utilización única en cada nueva conexión no confiable. No puedes usar los dos métodos."
101 login_label: 'Nombre de usuario (no se puede cambiar)'
101 name_label: 'Nombre' 102 name_label: 'Nombre'
102 email_label: 'Dirección de e-mail' 103 email_label: 'Dirección de correo electrónico'
103 twoFactorAuthentication_label: 'Autenticación en dos pasos' 104 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." 105 emailTwoFactor_label: 'Usando el correo electrónico (recibe un código por correo electrónico)'
106 googleTwoFactor_label: 'Usando una aplicación OTP (abre la aplicación, por ejemplo Google Authenticator, Authy o FreeOTP, para conseguir un código de utilización única)'
107 table_method: 'Método'
108 table_state: 'Estado'
109 table_action: 'Acción'
110 state_enabled: 'Activado'
111 state_disabled: 'Desactivado'
112 action_email: 'Usar correo electrónico'
113 action_app: 'Usar aplicación OTP'
105 delete: 114 delete:
106 title: Eliminar mi cuenta (Zona peligrosa) 115 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. 116 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.
@@ -113,7 +122,7 @@ config:
113 annotations: Eliminar TODAS las anotaciones 122 annotations: Eliminar TODAS las anotaciones
114 tags: Eliminar TODAS las etiquetas 123 tags: Eliminar TODAS las etiquetas
115 entries: Eliminar TODOS los artículos 124 entries: Eliminar TODOS los artículos
116 # archived: Remove ALL archived entries 125 archived: Eliminar TODOS los artículos archivados
117 confirm: ¿Estás completamente seguro? (NO SE PUEDE DESHACER) 126 confirm: ¿Estás completamente seguro? (NO SE PUEDE DESHACER)
118 form_password: 127 form_password:
119 description: "Puedes cambiar la contraseña aquí. Tu nueva contraseña debe tener al menos 8 caracteres." 128 description: "Puedes cambiar la contraseña aquí. Tu nueva contraseña debe tener al menos 8 caracteres."
@@ -127,6 +136,15 @@ config:
127 edit_rule_label: 'editar' 136 edit_rule_label: 'editar'
128 rule_label: 'Regla' 137 rule_label: 'Regla'
129 tags_label: 'Etiquetas' 138 tags_label: 'Etiquetas'
139 card:
140 new_tagging_rule: Crear una regla de etiquetado
141 import_tagging_rules: Importar reglas de etiquetado
142 import_tagging_rules_detail: Debes seleccionar un archivo JSON exportado previamente.
143 export_tagging_rules: Exportar reglas de etiquetado
144 export_tagging_rules_detail: Un archivo JSON será descargado y este podrá ser utilizado para volver a importar las reglas de etiquetado o como copia de seguridad.
145 file_label: Archivo JSON
146 import_submit: Importar
147 export: Exportar
130 faq: 148 faq:
131 title: 'Preguntas frecuentes' 149 title: 'Preguntas frecuentes'
132 tagging_rules_definition_title: '¿Qué significa « reglas de etiquetado automático »?' 150 tagging_rules_definition_title: '¿Qué significa « reglas de etiquetado automático »?'
@@ -157,8 +175,17 @@ config:
157 not_equal_to: 'Diferente de…' 175 not_equal_to: 'Diferente de…'
158 or: 'Una regla U otra' 176 or: 'Una regla U otra'
159 and: 'Una regla Y la otra' 177 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>' 178 matches: 'Prueba si un <i>sujeto</i> corresponde a una <i>búsqueda</i> (insensible a mayúsculas).<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>' 179 notmatches: 'Prueba si <i>subject</i> no corresponde a una <i>búsqueda</i> (insensible a mayúsculas).<br />Example: <code>title notmatches "fútbol"</code>'
180 otp:
181 page_title: Autenticación de dos pasos
182 app:
183 two_factor_code_description_1: Acabas de activar la autenticación en dos factores con OTP, abre tu aplicación OTP y consigue un código de utilización única. Desaparecerá al volver a cargar la página.
184 two_factor_code_description_2: 'Puedes escanear el código QR con tu aplicación:'
185 two_factor_code_description_3: 'No olvides guardar los códigos de seguridad en un lugar seguro, los puedes utilizar en caso de que pierdas el accesso a tu aplicación OTP:'
186 two_factor_code_description_4: 'Prueba un código generado por tu aplicación OTP:'
187 cancel: Cancelar
188 enable: Activar
162 189
163entry: 190entry:
164 default_title: 'Título del artículo' 191 default_title: 'Título del artículo'
@@ -192,8 +219,8 @@ entry:
192 unread_label: 'Sin leer' 219 unread_label: 'Sin leer'
193 preview_picture_label: 'Tiene imagen de previsualización' 220 preview_picture_label: 'Tiene imagen de previsualización'
194 preview_picture_help: 'Imagen de previsualización' 221 preview_picture_help: 'Imagen de previsualización'
195 # is_public_label: 'Has a public link' 222 is_public_label: 'Tiene un enlace público'
196 # is_public_help: 'Public link' 223 is_public_help: 'Enlace público'
197 language_label: 'Idioma' 224 language_label: 'Idioma'
198 http_status_label: 'Código de estado HTTP' 225 http_status_label: 'Código de estado HTTP'
199 reading_time: 226 reading_time:
@@ -220,7 +247,7 @@ entry:
220 delete: 'Eliminar' 247 delete: 'Eliminar'
221 add_a_tag: 'Añadir una etiqueta' 248 add_a_tag: 'Añadir una etiqueta'
222 share_content: 'Compartir' 249 share_content: 'Compartir'
223 share_email_label: 'e-mail' 250 share_email_label: 'Correo electrónico'
224 public_link: 'enlace público' 251 public_link: 'enlace público'
225 delete_public_link: 'eliminar enlace público' 252 delete_public_link: 'eliminar enlace público'
226 export: 'Exportar' 253 export: 'Exportar'
@@ -232,12 +259,12 @@ entry:
232 original_article: 'original' 259 original_article: 'original'
233 annotations_on_the_entry: '{0} Sin anotaciones|{1} Una anotación|]1,Inf[ %count% anotaciones' 260 annotations_on_the_entry: '{0} Sin anotaciones|{1} Una anotación|]1,Inf[ %count% anotaciones'
234 created_at: 'Fecha de creación' 261 created_at: 'Fecha de creación'
235 # published_at: 'Publication date' 262 published_at: 'Fecha de publicación'
236 # published_by: 'Published by' 263 published_by: 'Publicado por'
237 # provided_by: 'Provided by' 264 provided_by: 'Proporcionado por'
238 new: 265 new:
239 page_title: 'Guardar un nuevo artículo' 266 page_title: 'Guardar un nuevo artículo'
240 placeholder: 'http://sitioweb.com' 267 placeholder: 'https://sitioweb.es'
241 form_new: 268 form_new:
242 url_label: URL 269 url_label: URL
243 search: 270 search:
@@ -246,13 +273,18 @@ entry:
246 page_title: 'Editar un artículo' 273 page_title: 'Editar un artículo'
247 title_label: 'Título' 274 title_label: 'Título'
248 url_label: 'URL' 275 url_label: 'URL'
249 # origin_url_label: 'Origin url (from where you found that entry)' 276 origin_url_label: 'URL de origen (de nde has encontrado este artículo)'
250 save_label: 'Guardar' 277 save_label: 'Guardar'
251 public: 278 public:
252 shared_by_wallabag: "Este artículo se ha compartido con <a href='%wallabag_instance%'>wallabag</a>" 279 shared_by_wallabag: "Este artículo se ha compartido con <a href='%wallabag_instance%'>wallabag</a>"
253 confirm: 280 confirm:
254 # delete: "Are you sure you want to remove that article?" 281 delete: "¿Estás seguro de que quieres eliminar este artículo?"
255 # delete_tag: "Are you sure you want to remove that tag from that article?" 282 delete_tag: "¿Estás seguro de que quieres eliminar esta etiqueta de este artículo?"
283 metadata:
284 reading_time: "Tiempo de lectura estimado"
285 reading_time_minutes_short: "%readingTime% min"
286 address: "Dirección"
287 added_on: "Añadido el"
256 288
257about: 289about:
258 page_title: 'Acerca de' 290 page_title: 'Acerca de'
@@ -274,14 +306,14 @@ about:
274 bug_reports: 'Reporte de errores' 306 bug_reports: 'Reporte de errores'
275 support: '<a href="https://github.com/wallabag/wallabag/issues">en GitHub</a>' 307 support: '<a href="https://github.com/wallabag/wallabag/issues">en GitHub</a>'
276 helping: 308 helping:
277 description: 'wallabag es software libre y gratuito. Usted puede ayudarnos :' 309 description: 'wallabag es software libre y gratuito. Usted puede ayudarnos:'
278 by_contributing: 'contribuyendo al proyecto :' 310 by_contributing: 'contribuyendo al proyecto :'
279 by_contributing_2: 'nuestras necesidades están en un ticket' 311 by_contributing_2: 'nuestras necesidades están en un ticket'
280 by_paypal: 'vía Paypal' 312 by_paypal: 'vía Paypal'
281 contributors: 313 contributors:
282 description: 'Gracias a los colaboradores de la aplicación web de wallabag' 314 description: 'Gracias a los colaboradores de la aplicación web de wallabag'
283 third_party: 315 third_party:
284 description: 'Aquí está la lista de bibliotecas de terceros utilizadas por wallabag (con sus licencias) :' 316 description: 'Aquí está la lista de bibliotecas de terceros utilizadas por wallabag (con sus licencias):'
285 package: 'Paquete' 317 package: 'Paquete'
286 license: 'Licencia' 318 license: 'Licencia'
287 319
@@ -329,7 +361,7 @@ howto:
329 article_title: Atajos de teclado disponibles en el artículo 361 article_title: Atajos de teclado disponibles en el artículo
330 open_original: Abrir la URL original de un artículo 362 open_original: Abrir la URL original de un artículo
331 toggle_favorite: Marcar como favorito / no favorito el artículo 363 toggle_favorite: Marcar como favorito / no favorito el artículo
332 toggle_archive: marcar como leído / no leído el artículo 364 toggle_archive: Marcar como leído / no leído el artículo
333 delete: Borrar el artículo 365 delete: Borrar el artículo
334 material_title: Atajos de teclado disponibles solo en el tema Material 366 material_title: Atajos de teclado disponibles solo en el tema Material
335 add_link: Añadir un nuevo artículo 367 add_link: Añadir un nuevo artículo
@@ -348,7 +380,7 @@ quickstart:
348 title: 'Configure la aplicación' 380 title: 'Configure la aplicación'
349 description: 'Para que la aplicación se ajuste a tus necesidades, echa un vistazo a la configuración de wallabag.' 381 description: 'Para que la aplicación se ajuste a tus necesidades, echa un vistazo a la configuración de wallabag.'
350 language: 'Cambie el idioma y el diseño' 382 language: 'Cambie el idioma y el diseño'
351 rss: 'Activar los feeds RSS' 383 feed: 'Activar los feeds RSS'
352 tagging_rules: 'Escribe reglas para etiquetar automáticamente tus artículos' 384 tagging_rules: 'Escribe reglas para etiquetar automáticamente tus artículos'
353 admin: 385 admin:
354 title: 'Administración' 386 title: 'Administración'
@@ -388,7 +420,7 @@ quickstart:
388 title: 'Apoyo' 420 title: 'Apoyo'
389 description: 'Si necesitas ayuda, estamos a tu disposición.' 421 description: 'Si necesitas ayuda, estamos a tu disposición.'
390 github: 'En GitHub' 422 github: 'En GitHub'
391 email: 'Por e-mail' 423 email: 'Por correo electrónico'
392 gitter: 'En Gitter' 424 gitter: 'En Gitter'
393 425
394tag: 426tag:
@@ -396,12 +428,16 @@ tag:
396 list: 428 list:
397 number_on_the_page: '{0} No hay ninguna etiqueta.|{1} Hay una etiqueta.|]1,Inf[ Hay %count% etiquetas.' 429 number_on_the_page: '{0} No hay ninguna etiqueta.|{1} Hay una etiqueta.|]1,Inf[ Hay %count% etiquetas.'
398 see_untagged_entries: 'Ver artículos sin etiquetas' 430 see_untagged_entries: 'Ver artículos sin etiquetas'
431 no_untagged_entries: 'No hay artículos sin etiquetas.'
399 new: 432 new:
400 add: 'Añadir' 433 add: 'Añadir'
401 placeholder: 'Puedes añadir varias etiquetas, separadas por una coma.' 434 placeholder: 'Puedes añadir varias etiquetas, separadas por una coma.'
435 rename:
436 placeholder: 'Puedes actualizar el nombre de la etiqueta.'
402 437
403# export: 438export:
404# 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>' 439 footer_template: '<div style="text-align:center;"><p>Producido por wallabag con %method%</p><p>Por favor abre <a href="https://github.com/wallabag/wallabag/issues">un ticket</a> si tienes algún problema con la visualización de este E-Book en tu dispositivo.</p></div>'
440 unknown: 'Desconocido'
405 441
406import: 442import:
407 page_title: 'Importar' 443 page_title: 'Importar'
@@ -421,7 +457,7 @@ import:
421 admin_message: 'Debe definir %keyurls%una clave del API Pocket%keyurle%.' 457 admin_message: 'Debe definir %keyurls%una clave del API Pocket%keyurle%.'
422 user_message: 'El administrador de su servidor debe definir una clave del API Pocket.' 458 user_message: 'El administrador de su servidor debe definir una clave del API Pocket.'
423 authorize_message: 'Puede importar sus datos desde su cuenta de Pocket. Sólo tiene que hacer clic el botón para autorizar que wallabag se conecte a getpocket.com.' 459 authorize_message: 'Puede importar sus datos desde su cuenta de Pocket. Sólo tiene que hacer clic el botón para autorizar que wallabag se conecte a getpocket.com.'
424 connect_to_pocket: 'Conectar a Pocket e importar los datos' 460 connect_to_pocket: 'Conectarse a Pocket e importar los datos'
425 wallabag_v1: 461 wallabag_v1:
426 page_title: 'Importar > Wallabag v1' 462 page_title: 'Importar > Wallabag v1'
427 description: 'Importa todos tus artículos de wallabag v1. En la configuración de wallabag v1, haga clic en "Exportar JSON" dentro de la sección "Exportar datos de wallabag". Obtendrás un archivo llamado "wallabag-export-1-xxxx-xx-xx.json".' 463 description: 'Importa todos tus artículos de wallabag v1. En la configuración de wallabag v1, haga clic en "Exportar JSON" dentro de la sección "Exportar datos de wallabag". Obtendrás un archivo llamado "wallabag-export-1-xxxx-xx-xx.json".'
@@ -429,13 +465,16 @@ import:
429 wallabag_v2: 465 wallabag_v2:
430 page_title: 'Importar > Wallabag v2' 466 page_title: 'Importar > Wallabag v2'
431 description: 'Importa todos tus artículos de wallabag v2. En la sección Todos los artículos, en la barra lateral, haga clic en "JSON". Obtendrás un archivo llamado "All articles.json".' 467 description: 'Importa todos tus artículos de wallabag v2. En la sección Todos los artículos, en la barra lateral, haga clic en "JSON". Obtendrás un archivo llamado "All articles.json".'
468 # elcurator:
469 # page_title: 'Import > elCurator'
470 # description: 'This importer will import all your elCurator articles. Go to your preferences in your elCurator account and then, export your content. You will have a JSON file.'
432 readability: 471 readability:
433 page_title: 'Importar > Readability' 472 page_title: 'Importar > Readability'
434 description: 'Importa todos tus artículos de Readability. En la página de herramientas (https://www.readability.com/tools/), haga clic en "Exportar tus datos" en la sección "Exportar datos". Recibirás un e-mail para descargar un JSON (que no tiene extensión .json).' 473 description: 'Importa todos tus artículos de Readability. En la página de herramientas (https://www.readability.com/tools/), haga clic en "Exportar tus datos" en la sección "Exportar datos". Recibirás un correo electrónico para descargar un JSON (que no tiene extensión .json).'
435 how_to: 'Seleccione el archivo exportado de Readability y haga clic en el botón para subirlo e importarlo.' 474 how_to: 'Seleccione el archivo exportado de Readability y haga clic en el botón para subirlo e importarlo.'
436 worker: 475 worker:
437 enabled: "La importación se realiza de forma asíncrona. Una vez que la tarea de importación ha comenzado, un trabajador externo se encargará de los artículos uno a uno. El servicio actual es:" 476 enabled: "La importación se realiza de forma asíncrona. Una vez que la tarea de importación ha comenzado, un trabajador externo se encargará de los artículos uno a uno. El servicio actual es:"
438 download_images_warning: "Tienes activado descargar imágenes de los artículos. Esto justo con la importación clásica de artículos puede tardar mucho tiempo en ser procesado (o incluso fallar). <strong>Recomendamos encarecidamente</strong> habilitar la importación asíncrona para evitar errores." 477 download_images_warning: "Tienes activado descargar imágenes de los artículos. Esto junto con la importación clásica de artículos puede tardar mucho tiempo en ser procesado (o incluso fallar). <strong>Recomendamos encarecidamente</strong> habilitar la importación asíncrona para evitar errores."
439 firefox: 478 firefox:
440 page_title: 'Importar > Firefox' 479 page_title: 'Importar > Firefox'
441 description: "Importa todos tus marcadores de Firefox. En la ventana de marcadores (Ctrl+Mayus+O), en \"Importar y respaldar\", elige \"Copiar...\". Obtendrás un archivo .json." 480 description: "Importa todos tus marcadores de Firefox. En la ventana de marcadores (Ctrl+Mayus+O), en \"Importar y respaldar\", elige \"Copiar...\". Obtendrás un archivo .json."
@@ -482,6 +521,7 @@ developer:
482 redirect_uris_label: 'URIs de redirección' 521 redirect_uris_label: 'URIs de redirección'
483 save_label: 'Crear un nuevo cliente' 522 save_label: 'Crear un nuevo cliente'
484 action_back: 'Volver' 523 action_back: 'Volver'
524 copy_to_clipboard: 'Copiar'
485 client_parameter: 525 client_parameter:
486 page_title: 'Gestión de clientes API > Parámetros del cliente' 526 page_title: 'Gestión de clientes API > Parámetros del cliente'
487 page_description: 'Aquí están los parámetros del cliente.' 527 page_description: 'Aquí están los parámetros del cliente.'
@@ -493,14 +533,14 @@ developer:
493 howto: 533 howto:
494 page_title: 'Gestión de clientes API > Cómo crear mi primera aplicación' 534 page_title: 'Gestión de clientes API > Cómo crear mi primera aplicación'
495 description: 535 description:
496 paragraph_1: 'Los siguientes comandos hacen uso de la <a href="https://github.com/jkbrzt/httpie">biblioteca HTTPie</a>. Compruebe que está instalada en su sistema antes de usarla.' 536 paragraph_1: 'Los siguientes comandos hacen uso de la <a href="https://github.com/jkbrzt/httpie">biblioteca HTTPie</a>. Comprueba que está instalada en tu sistema antes de usarla.'
497 paragraph_2: 'Necesitas un token para establecer la comunicación entre una aplicación de terceros y la API de wallabag.' 537 paragraph_2: 'Necesitas un token para establecer la comunicación entre una aplicación de terceros y la API de wallabag.'
498 paragraph_3: 'Para crear este token, necesitas <a href="%link%">crear un nuevo cliente</a>.' 538 paragraph_3: 'Para crear este token, necesitas <a href="%link%">crear un nuevo cliente</a>.'
499 paragraph_4: 'Ahora crea tu token (reemplace client_id, client_secret, username y password con los valores generados):' 539 paragraph_4: 'Ahora crea tu token (reemplaza client_id, client_secret, username y password con los valores generados):'
500 paragraph_5: 'Este API devolverá una respuesta como esta:' 540 paragraph_5: 'Este API devolverá una respuesta como esta:'
501 paragraph_6: 'El access_token es útil para llamar a los métodos del API. Por ejemplo:' 541 paragraph_6: 'El access_token es útil para llamar a los métodos del API. Por ejemplo:'
502 paragraph_7: 'Esta llamada devolverá todos los artículos de tu usuario.' 542 paragraph_7: 'Esta llamada devolverá todos los artículos de tu usuario.'
503 paragraph_8: 'Si quiere ver todos los métodos del API, puede verlos en <a href="%link%">nuestra documentación del API</a>.' 543 paragraph_8: 'Si quieres ver todos los métodos del API, puedes verlos en <a href="%link%">nuestra documentación del API</a>.'
504 back: 'Volver' 544 back: 'Volver'
505 545
506user: 546user:
@@ -520,36 +560,37 @@ user:
520 password_label: 'Contraseña' 560 password_label: 'Contraseña'
521 repeat_new_password_label: 'Confirmar la contraseña' 561 repeat_new_password_label: 'Confirmar la contraseña'
522 plain_password_label: '????' 562 plain_password_label: '????'
523 email_label: 'E-mail' 563 email_label: 'Correo electrónico'
524 enabled_label: 'Activado' 564 enabled_label: 'Activado'
525 last_login_label: 'Último inicio de sesión' 565 last_login_label: 'Último inicio de sesión'
526 twofactor_label: Autenticación en dos pasos 566 twofactor_email_label: 'Autenticación de dos pasos por correo electrónico'
567 twofactor_google_label: 'Autenticación de dos pasos por aplicación OTP'
527 save: Guardar 568 save: Guardar
528 delete: Eliminar 569 delete: Eliminar
529 delete_confirm: ¿Estás seguro? 570 delete_confirm: ¿Estás seguro?
530 back_to_list: Volver a la lista 571 back_to_list: Volver a la lista
531 search: 572 search:
532 # placeholder: Filter by username or email 573 placeholder: 'Filtrar por nombre de usuario o correo electrónico'
533 574
534site_credential: 575site_credential:
535 # page_title: Site credentials management 576 page_title: 'Gestión de credenciales del sitio'
536 # new_site_credential: Create a credential 577 new_site_credential: 'Crear una credencial'
537 # edit_site_credential: Edit an existing credential 578 edit_site_credential: 'Editar una credencial existente'
538 # description: "Here you can manage all credentials for sites which required them (create, edit and delete), like a paywall, an authentication, etc." 579 description: "Aquí puedes gestionar todas las credenciales para los sitios que las necesiten (crear, editar y borrar), como un paywall, una autenticación, etc."
539 # list: 580 list:
540 # actions: Actions 581 actions: 'Acciones'
541 # edit_action: Edit 582 edit_action: 'Editar'
542 # yes: Yes 583 yes: 'Sí'
543 # no: No 584 no: 'No'
544 # create_new_one: Create a new credential 585 create_new_one: 'Crear una nueva credencial'
545 # form: 586 form:
546 # username_label: 'Username' 587 username_label: 'Nombre de usuario'
547 # host_label: 'Host' 588 host_label: 'Host (subdominio.ejemplo.org, .ejemplo.org, etc.)'
548 # password_label: 'Password' 589 password_label: 'Contraseña'
549 # save: Save 590 save: 'Guardar'
550 # delete: Delete 591 delete: 'Borrar'
551 # delete_confirm: Are you sure? 592 delete_confirm: '¿Estás seguro?'
552 # back_to_list: Back to list 593 back_to_list: 'Volver a la lista'
553 594
554error: 595error:
555 page_title: Ha ocurrido un error 596 page_title: Ha ocurrido un error
@@ -561,14 +602,18 @@ flashes:
561 password_updated: 'Contraseña actualizada' 602 password_updated: 'Contraseña actualizada'
562 password_not_updated_demo: "En el modo demo, no puede cambiar la contraseña del usuario." 603 password_not_updated_demo: "En el modo demo, no puede cambiar la contraseña del usuario."
563 user_updated: 'Información actualizada' 604 user_updated: 'Información actualizada'
564 rss_updated: 'Configuración RSS actualizada' 605 feed_updated: 'Configuración RSS actualizada'
565 tagging_rules_updated: 'Regla de etiquetado actualizada' 606 tagging_rules_updated: 'Regla de etiquetado actualizada'
566 tagging_rules_deleted: 'Regla de etiquetado eliminada' 607 tagging_rules_deleted: 'Regla de etiquetado eliminada'
567 rss_token_updated: 'Token RSS actualizado' 608 feed_token_updated: 'Token RSS actualizado'
609 feed_token_revoked: 'Token RSS revocado'
568 annotations_reset: Anotaciones reiniciadas 610 annotations_reset: Anotaciones reiniciadas
569 tags_reset: Etiquetas reiniciadas 611 tags_reset: Etiquetas reiniciadas
570 entries_reset: Artículos reiniciados 612 entries_reset: Artículos reiniciados
571 # archived_reset: Archived entries deleted 613 archived_reset: Artículos archivados borrados
614 otp_enabled: Autenticación de dos pasos activada
615 tagging_rules_imported: Reglas de etiquetado importadas
616 tagging_rules_not_imported: Un error se ha producico en la importación de las reglas de etiquetado
572 entry: 617 entry:
573 notice: 618 notice:
574 entry_already_saved: 'Artículo ya guardado el %fecha%' 619 entry_already_saved: 'Artículo ya guardado el %fecha%'
@@ -582,9 +627,11 @@ flashes:
582 entry_starred: 'Artículo marcado como favorito' 627 entry_starred: 'Artículo marcado como favorito'
583 entry_unstarred: 'Artículo desmarcado como favorito' 628 entry_unstarred: 'Artículo desmarcado como favorito'
584 entry_deleted: 'Artículo eliminado' 629 entry_deleted: 'Artículo eliminado'
630 no_random_entry: 'Ningún artículo con esos criterios fue encontrado'
585 tag: 631 tag:
586 notice: 632 notice:
587 tag_added: 'Etiqueta añadida' 633 tag_added: 'Etiqueta añadida'
634 tag_renamed: 'Etiqueta renombrada'
588 import: 635 import:
589 notice: 636 notice:
590 failed: 'Importación fallida, por favor, inténtelo de nuevo.' 637 failed: 'Importación fallida, por favor, inténtelo de nuevo.'
@@ -596,15 +643,15 @@ flashes:
596 rabbit_enabled_not_installed: RabbitMQ está activado para gestionar la importación asíncrona pero parece que <u>no se puede conectar</u>. Por favor, comprueba la configuración de RabbitMQ. 643 rabbit_enabled_not_installed: RabbitMQ está activado para gestionar la importación asíncrona pero parece que <u>no se puede conectar</u>. Por favor, comprueba la configuración de RabbitMQ.
597 developer: 644 developer:
598 notice: 645 notice:
599 client_created: 'Creado el cliente %name%.' 646 client_created: 'El cliente %name% ha sido creado.'
600 client_deleted: 'Eliminado el cliente %name%' 647 client_deleted: 'El cliente %name% ha sido eliminado'
601 user: 648 user:
602 notice: 649 notice:
603 added: 'Añadido el usuario "%username%"' 650 added: 'El usuario "%username%" ha sido añadido'
604 updated: 'Actualizado el usuario "%username%"' 651 updated: 'El usuario "%username%" ha sido actualizado'
605 deleted: 'Eliminado el usuario "%username%"' 652 deleted: 'El usuario "%username%" ha sido eliminado'
606 site_credential: 653 site_credential:
607 notice: 654 notice:
608 # added: 'Site credential for "%host%" added' 655 added: 'Credenciales del sitio añadidas para "%host%"'
609 # updated: 'Site credential for "%host%" updated' 656 updated: 'Credenciales del sitio actualizadas para "%host%"'
610 # deleted: 'Site credential for "%host%" deleted' 657 deleted: 'Credenciales del sitio eliminadas para "%host%"'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml
index b3f2eb58..c1fb74d3 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml
@@ -33,10 +33,12 @@ menu:
33 back_to_unread: 'بازگشت به خوانده‌نشده‌ها' 33 back_to_unread: 'بازگشت به خوانده‌نشده‌ها'
34 # users_management: 'Users management' 34 # users_management: 'Users management'
35 # site_credentials: 'Site credentials' 35 # site_credentials: 'Site credentials'
36 quickstart: "Quickstart"
36 top: 37 top:
37 add_new_entry: 'افزودن مقالهٔ تازه' 38 add_new_entry: 'افزودن مقالهٔ تازه'
38 search: 'جستجو' 39 search: 'جستجو'
39 filter_entries: 'فیلترکردن مقاله‌ها' 40 filter_entries: 'فیلترکردن مقاله‌ها'
41 # random_entry: Jump to a random entry from that list
40 export: 'برون‌بری' 42 export: 'برون‌بری'
41 search_form: 43 search_form:
42 input_label: 'جستجوی خود را این‌جا بنویسید:' 44 input_label: 'جستجوی خود را این‌جا بنویسید:'
@@ -53,11 +55,12 @@ config:
53 page_title: 'پیکربندی' 55 page_title: 'پیکربندی'
54 tab_menu: 56 tab_menu:
55 settings: 'تنظیمات' 57 settings: 'تنظیمات'
56 rss: 'آر-اس-اس' 58 feed: 'آر-اس-اس'
57 user_info: 'اطلاعات کاربر' 59 user_info: 'اطلاعات کاربر'
58 password: 'رمز' 60 password: 'رمز'
59 rules: 'برچسب‌گذاری خودکار' 61 rules: 'برچسب‌گذاری خودکار'
60 new_user: 'افزودن کاربر' 62 new_user: 'افزودن کاربر'
63 # reset: 'Reset area'
61 form: 64 form:
62 save: 'ذخیره' 65 save: 'ذخیره'
63 form_settings: 66 form_settings:
@@ -65,12 +68,8 @@ config:
65 items_per_page_label: 'تعداد مقاله در هر صفحه' 68 items_per_page_label: 'تعداد مقاله در هر صفحه'
66 language_label: 'زبان' 69 language_label: 'زبان'
67 reading_speed: 70 reading_speed:
68 label: 'سرعت خواندن' 71 # label: 'Reading speed (words per minute)'
69 help_message: 'سرعت خواندن‌تان را با ابزارهای آنلاین تخمین بزنید:' 72 help_message: 'سرعت خواندن‌تان را با ابزارهای آنلاین تخمین بزنید:'
70 100_word: 'من تقریباً ۱۰۰ واژه را در دقیقه می‌خوانم'
71 200_word: 'من تقریباً ۲۰۰ واژه را در دقیقه می‌خوانم'
72 300_word: 'من تقریباً ۳۰۰ واژه را در دقیقه می‌خوانم'
73 400_word: 'من تقریباً ۴۰۰ واژه را در دقیقه می‌خوانم'
74 action_mark_as_read: 73 action_mark_as_read:
75 # label: 'Where do you want to be redirected to after marking an article as read?' 74 # label: 'Where do you want to be redirected to after marking an article as read?'
76 # redirect_homepage: 'To the homepage' 75 # redirect_homepage: 'To the homepage'
@@ -83,25 +82,35 @@ 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." 82 # 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." 83 # 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." 84 # help_pocket_consumer_key: "Required for Pocket import. You can create it in your Pocket account."
86 form_rss: 85 form_feed:
87 description: 'با خوراک آر-اس-اس که wallabag در اختیارتان می‌گذارد، می‌توانید مقاله‌های ذخیره‌شده را در نرم‌افزار آر-اس-اس دلخواه خود بخوانید. برای این کار نخست باید یک کد بسازید.' 86 description: 'با خوراک آر-اس-اس که wallabag در اختیارتان می‌گذارد، می‌توانید مقاله‌های ذخیره‌شده را در نرم‌افزار آر-اس-اس دلخواه خود بخوانید. برای این کار نخست باید یک کد بسازید.'
88 token_label: 'کد آر-اس-اس' 87 token_label: 'کد آر-اس-اس'
89 no_token: 'بدون کد' 88 no_token: 'بدون کد'
90 token_create: 'کد خود را بسازید' 89 token_create: 'کد خود را بسازید'
91 token_reset: 'بازنشانی کد' 90 token_reset: 'بازنشانی کد'
92 rss_links: 'پیوند آر-اس-اس' 91 # token_revoke: 'Revoke the token'
93 rss_link: 92 feed_links: 'پیوند آر-اس-اس'
93 feed_link:
94 unread: 'خوانده‌نشده' 94 unread: 'خوانده‌نشده'
95 starred: 'برگزیده' 95 starred: 'برگزیده'
96 archive: 'بایگانی' 96 archive: 'بایگانی'
97 # all: 'All' 97 # all: 'All'
98 rss_limit: 'محدودیت آر-اس-اس' 98 feed_limit: 'محدودیت آر-اس-اس'
99 form_user: 99 form_user:
100 two_factor_description: "با فعال‌کردن تأیید ۲مرحله‌ای هر بار که اتصال تأییدنشده‌ای برقرار شد، به شما یک کد از راه ایمیل فرستاده می‌شود" 100 # 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 # login_label: 'Login (can not be changed)'
101 name_label: 'نام' 102 name_label: 'نام'
102 email_label: 'نشانی ایمیل' 103 email_label: 'نشانی ایمیل'
103 twoFactorAuthentication_label: 'تأیید ۲مرحله‌ای' 104 two_factor:
104 # help_twoFactorAuthentication: "If you enable 2FA, each time you want to login to wallabag, you'll receive a code by email." 105 # emailTwoFactor_label: 'Using email (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.
@@ -127,6 +136,15 @@ config:
127 # edit_rule_label: 'edit' 136 # edit_rule_label: 'edit'
128 rule_label: 'قانون' 137 rule_label: 'قانون'
129 tags_label: 'برچسب‌ها' 138 tags_label: 'برچسب‌ها'
139 # card:
140 # new_tagging_rule: Create a tagging rule
141 # import_tagging_rules: Import tagging rules
142 # import_tagging_rules_detail: You have to select the JSON file you previously exported.
143 # export_tagging_rules: Export tagging rules
144 # export_tagging_rules_detail: This will download a JSON file that you can use to import tagging rules elsewhere or to backup them.
145 # file_label: JSON file
146 # import_submit: Import
147 # export: Export
130 faq: 148 faq:
131 title: 'پرسش‌های متداول' 149 title: 'پرسش‌های متداول'
132 tagging_rules_definition_title: 'برچسب‌گذاری خودکار یعنی چه؟' 150 tagging_rules_definition_title: 'برچسب‌گذاری خودکار یعنی چه؟'
@@ -159,6 +177,15 @@ config:
159 # and: 'One rule AND another' 177 # 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>' 178 # 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>' 179 # notmatches: 'Tests that a <i>subject</i> doesn''t match match a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
180 otp:
181 # page_title: Two-factor authentication
182 # app:
183 # 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.
184 # two_factor_code_description_2: 'You can scan that QR Code with your app:'
185 # 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:'
186 # two_factor_code_description_4: 'Test an OTP code from your configured app:'
187 # cancel: Cancel
188 # enable: Enable
162 189
163entry: 190entry:
164 # default_title: 'Title of the entry' 191 # default_title: 'Title of the entry'
@@ -237,7 +264,7 @@ entry:
237 # provided_by: 'Provided by' 264 # provided_by: 'Provided by'
238 new: 265 new:
239 page_title: 'ذخیرهٔ مقالهٔ تازه' 266 page_title: 'ذخیرهٔ مقالهٔ تازه'
240 placeholder: 'http://website.com' 267 placeholder: 'https://website.ir'
241 form_new: 268 form_new:
242 url_label: نشانی 269 url_label: نشانی
243 search: 270 search:
@@ -253,6 +280,11 @@ entry:
253 confirm: 280 confirm:
254 # delete: "Are you sure you want to remove that article?" 281 # delete: "Are you sure you want to remove that article?"
255 # delete_tag: "Are you sure you want to remove that tag from that article?" 282 # delete_tag: "Are you sure you want to remove that tag from that article?"
283 metadata:
284 # reading_time: "Estimated reading time"
285 # reading_time_minutes_short: "%readingTime% min"
286 # address: "Address"
287 # added_on: "Added on"
256 288
257about: 289about:
258 page_title: 'درباره' 290 page_title: 'درباره'
@@ -348,7 +380,7 @@ quickstart:
348 title: 'برنامه را تنظیم کنید' 380 title: 'برنامه را تنظیم کنید'
349 # description: 'In order to have an application which suits you, have a look into the configuration of wallabag.' 381 # description: 'In order to have an application which suits you, have a look into the configuration of wallabag.'
350 language: 'زبان و نمای برنامه را تغییر دهید' 382 language: 'زبان و نمای برنامه را تغییر دهید'
351 rss: 'خوراک آر-اس-اس را فعال کنید' 383 feed: 'خوراک آر-اس-اس را فعال کنید'
352 tagging_rules: 'قانون‌های برچسب‌گذاری خودکار مقاله‌هایتان را تعریف کنید' 384 tagging_rules: 'قانون‌های برچسب‌گذاری خودکار مقاله‌هایتان را تعریف کنید'
353 admin: 385 admin:
354 title: 'مدیریت' 386 title: 'مدیریت'
@@ -396,12 +428,16 @@ tag:
396 list: 428 list:
397 number_on_the_page: '{0} هیچ برچسبی نیست.|{1} یک برچسب هست.|]1,Inf[ %count% برچسب هست.' 429 number_on_the_page: '{0} هیچ برچسبی نیست.|{1} یک برچسب هست.|]1,Inf[ %count% برچسب هست.'
398 # see_untagged_entries: 'See untagged entries' 430 # see_untagged_entries: 'See untagged entries'
431 # no_untagged_entries: 'There are no untagged entries.'
399 new: 432 new:
400 # add: 'Add' 433 # add: 'Add'
401 # placeholder: 'You can add several tags, separated by a comma.' 434 # placeholder: 'You can add several tags, separated by a comma.'
435 rename:
436 # placeholder: 'You can update tag name.'
402 437
403# export: 438# export:
404# 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>' 439# 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>'
440# unknown: 'Unknown'
405 441
406import: 442import:
407 page_title: 'درون‌ریزی' 443 page_title: 'درون‌ریزی'
@@ -429,6 +465,9 @@ import:
429 wallabag_v2: 465 wallabag_v2:
430 page_title: 'درون‌ریزی > Wallabag v2' 466 page_title: 'درون‌ریزی > Wallabag v2'
431 description: 'این برنامه همهٔ داده‌های شما را در نسخهٔ ۲ wallabag درون‌ریزی می‌کند. به بخش «همهٔ مقاله‌ها» بروید و در بخش «برون‌ریزی» روی "JSON" کلیک کنید. با این کار شما پرونده‌ای به شکل "All articles.json" دریافت خواهید کرد.' 467 description: 'این برنامه همهٔ داده‌های شما را در نسخهٔ ۲ wallabag درون‌ریزی می‌کند. به بخش «همهٔ مقاله‌ها» بروید و در بخش «برون‌ریزی» روی "JSON" کلیک کنید. با این کار شما پرونده‌ای به شکل "All articles.json" دریافت خواهید کرد.'
468 # elcurator:
469 # page_title: 'Import > elCurator'
470 # description: 'This importer will import all your elCurator articles. Go to your preferences in your elCurator account and then, export your content. You will have a JSON file.'
432 readability: 471 readability:
433 page_title: 'درون‌ریزی > Readability' 472 page_title: 'درون‌ریزی > Readability'
434 # description: 'This importer will import all your Readability articles. On the tools (https://www.readability.com/tools/) page, click on "Export your data" in the "Data Export" section. You will received an email to download a json (which does not end with .json in fact).' 473 # description: 'This importer will import all your Readability articles. On the tools (https://www.readability.com/tools/) page, click on "Export your data" in the "Data Export" section. You will received an email to download a json (which does not end with .json in fact).'
@@ -482,6 +521,7 @@ developer:
482 # redirect_uris_label: 'Redirect URIs' 521 # redirect_uris_label: 'Redirect URIs'
483 # save_label: 'Create a new client' 522 # save_label: 'Create a new client'
484 # action_back: 'بازگشت' 523 # action_back: 'بازگشت'
524 # copy_to_clipboard: Copy
485 # client_parameter: 525 # client_parameter:
486 # page_title: 'API clients management > Client parameters' 526 # page_title: 'API clients management > Client parameters'
487 # page_description: 'Here are your client parameters.' 527 # page_description: 'Here are your client parameters.'
@@ -523,7 +563,8 @@ user:
523 email_label: 'نشانی ایمیل' 563 email_label: 'نشانی ایمیل'
524 # enabled_label: 'Enabled' 564 # enabled_label: 'Enabled'
525 # last_login_label: 'Last login' 565 # last_login_label: 'Last login'
526 # twofactor_label: Two factor authentication 566 # twofactor_email_label: Two factor authentication by email
567 # twofactor_google_label: Two factor authentication by OTP app
527 # save: Save 568 # save: Save
528 # delete: Delete 569 # delete: Delete
529 # delete_confirm: Are you sure? 570 # delete_confirm: Are you sure?
@@ -544,7 +585,7 @@ site_credential:
544 # create_new_one: Create a new credential 585 # create_new_one: Create a new credential
545 # form: 586 # form:
546 # username_label: 'Username' 587 # username_label: 'Username'
547 # host_label: 'Host' 588 # host_label: 'Host (subdomain.example.org, .example.org, etc.)'
548 # password_label: 'Password' 589 # password_label: 'Password'
549 # save: Save 590 # save: Save
550 # delete: Delete 591 # delete: Delete
@@ -561,14 +602,18 @@ flashes:
561 password_updated: 'رمز به‌روز شد' 602 password_updated: 'رمز به‌روز شد'
562 password_not_updated_demo: "در حالت نمایشی نمی‌توانید رمز کاربر را عوض کنید." 603 password_not_updated_demo: "در حالت نمایشی نمی‌توانید رمز کاربر را عوض کنید."
563 user_updated: 'اطلاعات به‌روز شد' 604 user_updated: 'اطلاعات به‌روز شد'
564 rss_updated: 'اطلاعات آر-اس-اس به‌روز شد' 605 feed_updated: 'اطلاعات آر-اس-اس به‌روز شد'
565 tagging_rules_updated: 'برچسب‌گذاری خودکار به‌روز شد' 606 tagging_rules_updated: 'برچسب‌گذاری خودکار به‌روز شد'
566 tagging_rules_deleted: 'قانون برچسب‌گذاری پاک شد' 607 tagging_rules_deleted: 'قانون برچسب‌گذاری پاک شد'
567 rss_token_updated: 'کد آر-اس-اس به‌روز شد' 608 feed_token_updated: 'کد آر-اس-اس به‌روز شد'
609 # feed_token_revoked: 'RSS token revoked'
568 # annotations_reset: Annotations reset 610 # annotations_reset: Annotations reset
569 # tags_reset: Tags reset 611 # tags_reset: Tags reset
570 # entries_reset: Entries reset 612 # entries_reset: Entries reset
571 # archived_reset: Archived entries deleted 613 # archived_reset: Archived entries deleted
614 # otp_enabled: Two-factor authentication enabled
615 # tagging_rules_imported: Tagging rules imported
616 # tagging_rules_not_imported: Error while importing tagging rules
572 entry: 617 entry:
573 notice: 618 notice:
574 entry_already_saved: 'این مقاله در تاریخ %date% ذخیره شده بود' 619 entry_already_saved: 'این مقاله در تاریخ %date% ذخیره شده بود'
@@ -582,9 +627,11 @@ flashes:
582 entry_starred: 'مقاله برگزیده شد' 627 entry_starred: 'مقاله برگزیده شد'
583 entry_unstarred: 'مقاله نابرگزیده شد' 628 entry_unstarred: 'مقاله نابرگزیده شد'
584 entry_deleted: 'مقاله پاک شد' 629 entry_deleted: 'مقاله پاک شد'
630 # no_random_entry: 'No article with these criterias was found'
585 tag: 631 tag:
586 notice: 632 notice:
587 tag_added: 'برچسب افزوده شد' 633 tag_added: 'برچسب افزوده شد'
634 # tag_renamed: 'Tag renamed'
588 import: 635 import:
589 notice: 636 notice:
590 failed: 'درون‌ریزی شکست خورد. لطفاً دوباره تلاش کنید.' 637 failed: 'درون‌ریزی شکست خورد. لطفاً دوباره تلاش کنید.'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml
index 5cdd836e..2b8bb092 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml
@@ -5,7 +5,7 @@ security:
5 forgot_password: "Mot de passe oublié ?" 5 forgot_password: "Mot de passe oublié ?"
6 submit: "Se connecter" 6 submit: "Se connecter"
7 register: "Créer un compte" 7 register: "Créer un compte"
8 username: "Nom d’utilisateur" 8 username: "Identifiant"
9 password: "Mot de passe" 9 password: "Mot de passe"
10 cancel: "Annuler" 10 cancel: "Annuler"
11 resetting: 11 resetting:
@@ -33,10 +33,12 @@ menu:
33 back_to_unread: "Retour aux articles non lus" 33 back_to_unread: "Retour aux articles non lus"
34 users_management: "Gestion des utilisateurs" 34 users_management: "Gestion des utilisateurs"
35 site_credentials: 'Accès aux sites' 35 site_credentials: 'Accès aux sites'
36 quickstart: "Pour bien débuter"
36 top: 37 top:
37 add_new_entry: "Sauvegarder un nouvel article" 38 add_new_entry: "Sauvegarder un nouvel article"
38 search: "Rechercher" 39 search: "Rechercher"
39 filter_entries: "Filtrer les articles" 40 filter_entries: "Filtrer les articles"
41 random_entry: Aller à un article aléatoire de cette liste
40 export: "Exporter" 42 export: "Exporter"
41 search_form: 43 search_form:
42 input_label: "Saisissez votre terme de recherche" 44 input_label: "Saisissez votre terme de recherche"
@@ -53,11 +55,12 @@ config:
53 page_title: "Configuration" 55 page_title: "Configuration"
54 tab_menu: 56 tab_menu:
55 settings: "Paramètres" 57 settings: "Paramètres"
56 rss: "RSS" 58 feed: "Flux"
57 user_info: "Mon compte" 59 user_info: "Mon compte"
58 password: "Mot de passe" 60 password: "Mot de passe"
59 rules: "Règles de tag automatiques" 61 rules: "Règles de tag automatiques"
60 new_user: "Créer un compte" 62 new_user: "Créer un compte"
63 reset: "Réinitialisation"
61 form: 64 form:
62 save: "Enregistrer" 65 save: "Enregistrer"
63 form_settings: 66 form_settings:
@@ -65,14 +68,10 @@ config:
65 items_per_page_label: "Nombre d’articles par page" 68 items_per_page_label: "Nombre d’articles par page"
66 language_label: "Langue" 69 language_label: "Langue"
67 reading_speed: 70 reading_speed:
68 label: "Vitesse de lecture" 71 label: "Vitesse de lecture (mots par minute)"
69 help_message: "Vous pouvez utiliser un outil en ligne pour estimer votre vitesse de lecture :" 72 help_message: "Vous pouvez utiliser un outil en ligne pour estimer votre vitesse de lecture :"
70 100_word: "Je lis environ 100 mots par minute"
71 200_word: "Je lis environ 200 mots par minute"
72 300_word: "Je lis environ 300 mots par minute"
73 400_word: "Je lis environ 400 mots par minute"
74 action_mark_as_read: 73 action_mark_as_read:
75 label: "Que faire lorsqu'un article est supprimé, marqué comme lu ou marqué comme favoris ?" 74 label: "Que faire lorsqu'un article est supprimé, marqué comme lu ou marqué comme favori ?"
76 redirect_homepage: "Retourner à la page d’accueil" 75 redirect_homepage: "Retourner à la page d’accueil"
77 redirect_current_page: "Rester sur la page actuelle" 76 redirect_current_page: "Rester sur la page actuelle"
78 pocket_consumer_key_label: "Clé d’authentification Pocket pour importer les données" 77 pocket_consumer_key_label: "Clé d’authentification Pocket pour importer les données"
@@ -83,25 +82,35 @@ 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." 82 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." 83 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." 84 help_pocket_consumer_key: "Nécessaire pour l’import depuis Pocket. Vous pouvez le créer depuis votre compte Pocket."
86 form_rss: 85 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." 86 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" 87 token_label: "Jeton de flux"
89 no_token: "Aucun jeton généré" 88 no_token: "Aucun jeton généré"
90 token_create: "Créez votre jeton" 89 token_create: "Créez votre jeton"
91 token_reset: "Réinitialisez votre jeton" 90 token_reset: "Réinitialisez votre jeton"
92 rss_links: "Adresses de vos flux RSS" 91 token_revoke: 'Supprimer le jeton'
93 rss_link: 92 feed_links: "Adresses de vos flux"
93 feed_link:
94 unread: "Non lus" 94 unread: "Non lus"
95 starred: "Favoris" 95 starred: "Favoris"
96 archive: "Lus" 96 archive: "Lus"
97 all: "Tous" 97 all: "Tous"
98 rss_limit: "Nombre d’articles dans le flux" 98 feed_limit: "Nombre d’articles dans le flux"
99 form_user: 99 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." 100 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 login_label: 'Identifiant'
101 name_label: "Nom" 102 name_label: "Nom"
102 email_label: "Adresse courriel" 103 email_label: "Adresse courriel"
103 twoFactorAuthentication_label: "Double authentification" 104 two_factor:
104 help_twoFactorAuthentication: "Si vous activez 2FA, à chaque tentative de connexion à wallabag, vous recevrez un code par email." 105 emailTwoFactor_label: 'En utlisant l’email (recevez un code par email)'
106 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)'
107 table_method: Méthode
108 table_state: État
109 table_action: Action
110 state_enabled: Activé
111 state_disabled: Désactivé
112 action_email: Utiliser l'email
113 action_app: Utiliser une app OTP
105 delete: 114 delete:
106 title: "Supprimer mon compte (attention danger !)" 115 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é." 116 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é."
@@ -127,6 +136,15 @@ config:
127 edit_rule_label: "éditer" 136 edit_rule_label: "éditer"
128 rule_label: "Règle" 137 rule_label: "Règle"
129 tags_label: "Tags" 138 tags_label: "Tags"
139 card:
140 new_tagging_rule: Créer une règle
141 import_tagging_rules: Importer des règles
142 import_tagging_rules_detail: Vous devez sélectionné un fichier JSON que vous avez précédemment exporté.
143 export_tagging_rules: Exporter les règles
144 export_tagging_rules_detail: Un fichier JSON sera téléchargé et vous pourrez l'utiliser pour ré-importer les règles ou comme sauvegarde.
145 file_label: Fichier JSON
146 import_submit: Importer
147 export: Export
130 faq: 148 faq:
131 title: "FAQ" 149 title: "FAQ"
132 tagging_rules_definition_title: "Que signifient les règles de tag automatiques ?" 150 tagging_rules_definition_title: "Que signifient les règles de tag automatiques ?"
@@ -159,6 +177,15 @@ config:
159 and: "Une règle ET l’autre" 177 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>" 178 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>" 179 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>"
180 otp:
181 page_title: Authentification double-facteur
182 app:
183 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.
184 two_factor_code_description_2: 'Vous pouvez scanner le QR code avec votre application :'
185 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 :'
186 two_factor_code_description_4: 'Testez un code généré par votre application OTP :'
187 cancel: Annuler
188 enable: Activer
162 189
163entry: 190entry:
164 default_title: "Titre de l’article" 191 default_title: "Titre de l’article"
@@ -237,7 +264,7 @@ entry:
237 provided_by: "Fourni par" 264 provided_by: "Fourni par"
238 new: 265 new:
239 page_title: "Sauvegarder un nouvel article" 266 page_title: "Sauvegarder un nouvel article"
240 placeholder: "http://website.com" 267 placeholder: "https://website.fr"
241 form_new: 268 form_new:
242 url_label: "Adresse" 269 url_label: "Adresse"
243 search: 270 search:
@@ -253,6 +280,11 @@ entry:
253 confirm: 280 confirm:
254 delete: "Voulez-vous vraiment supprimer cet article ?" 281 delete: "Voulez-vous vraiment supprimer cet article ?"
255 delete_tag: "Voulez-vous vraiment supprimer ce tag de cet article ?" 282 delete_tag: "Voulez-vous vraiment supprimer ce tag de cet article ?"
283 metadata:
284 reading_time: "Durée de lecture estimée"
285 reading_time_minutes_short: "%readingTime% min"
286 address: "Adresse"
287 added_on: "Ajouté le"
256 288
257about: 289about:
258 page_title: "À propos" 290 page_title: "À propos"
@@ -290,7 +322,7 @@ howto:
290 tab_menu: 322 tab_menu:
291 add_link: "Ajouter un lien" 323 add_link: "Ajouter un lien"
292 shortcuts: "Utiliser les raccourcis" 324 shortcuts: "Utiliser les raccourcis"
293 page_description: "Il y a plusieurs façon d’enregistrer un article :" 325 page_description: "Il y a plusieurs façons d’enregistrer un article :"
294 top_menu: 326 top_menu:
295 browser_addons: "Extensions de navigateur" 327 browser_addons: "Extensions de navigateur"
296 mobile_apps: "Applications smartphone" 328 mobile_apps: "Applications smartphone"
@@ -348,7 +380,7 @@ quickstart:
348 title: "Configurez l’application" 380 title: "Configurez l’application"
349 description: "Pour voir une application qui vous correspond, allez voir du côté de la configuration de wallabag." 381 description: "Pour voir une application qui vous correspond, allez voir du côté de la configuration de wallabag."
350 language: "Changez la langue et le design de l’application" 382 language: "Changez la langue et le design de l’application"
351 rss: "Activez les flux RSS" 383 feed: "Activez les flux Atom"
352 tagging_rules: "Écrivez des règles pour classer automatiquement vos articles" 384 tagging_rules: "Écrivez des règles pour classer automatiquement vos articles"
353 admin: 385 admin:
354 title: "Administration" 386 title: "Administration"
@@ -396,12 +428,16 @@ tag:
396 list: 428 list:
397 number_on_the_page: "{0} Il n’y a pas de tag.|{1} Il y a un tag.|]1,Inf[ Il y a %count% tags." 429 number_on_the_page: "{0} Il n’y a pas de tag.|{1} Il y a un tag.|]1,Inf[ Il y a %count% tags."
398 see_untagged_entries: "Voir les articles sans tag" 430 see_untagged_entries: "Voir les articles sans tag"
431 no_untagged_entries: 'Aucun article sans tag.'
399 new: 432 new:
400 add: "Ajouter" 433 add: "Ajouter"
401 placeholder: "Vous pouvez ajouter plusieurs tags, séparés par une virgule." 434 placeholder: "Vous pouvez ajouter plusieurs tags, séparés par une virgule."
435 rename:
436 placeholder: 'Vous pouvez changer le nom de votre tag.'
402 437
403export: 438export:
404 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>' 439 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>'
440 unknown: 'Inconnu'
405 441
406import: 442import:
407 page_title: "Importer" 443 page_title: "Importer"
@@ -429,6 +465,9 @@ import:
429 wallabag_v2: 465 wallabag_v2:
430 page_title: "Importer > wallabag v2" 466 page_title: "Importer > wallabag v2"
431 description: "Cet outil va importer tous vos articles d’une autre instance de wallabag v2. Allez dans tous vos articles, puis, sur la barre latérale, cliquez sur « JSON ». Vous allez récupérer un fichier « All articles.json »" 467 description: "Cet outil va importer tous vos articles d’une autre instance de wallabag v2. Allez dans tous vos articles, puis, sur la barre latérale, cliquez sur « JSON ». Vous allez récupérer un fichier « All articles.json »"
468 # elcurator:
469 # page_title: 'Import > elCurator'
470 # description: 'This importer will import all your elCurator articles. Go to your preferences in your elCurator account and then, export your content. You will have a JSON file.'
432 readability: 471 readability:
433 page_title: "Importer > Readability" 472 page_title: "Importer > Readability"
434 description: "Cet outil va importer toutes vos données de Readability. Sur la page des outils (https://www.readability.com/tools/), cliquez sur « Export your data » dans la section « Data Export ». Vous allez recevoir un courriel avec un lien pour télécharger le json." 473 description: "Cet outil va importer toutes vos données de Readability. Sur la page des outils (https://www.readability.com/tools/), cliquez sur « Export your data » dans la section « Data Export ». Vous allez recevoir un courriel avec un lien pour télécharger le json."
@@ -482,6 +521,7 @@ developer:
482 redirect_uris_label: "Adresses de redirection (optionnel)" 521 redirect_uris_label: "Adresses de redirection (optionnel)"
483 save_label: "Créer un nouveau client" 522 save_label: "Créer un nouveau client"
484 action_back: "Retour" 523 action_back: "Retour"
524 copy_to_clipboard: Copier
485 client_parameter: 525 client_parameter:
486 page_title: "Gestion des clients API > Les paramètres de votre client" 526 page_title: "Gestion des clients API > Les paramètres de votre client"
487 page_description: "Voilà les paramètres de votre client" 527 page_description: "Voilà les paramètres de votre client"
@@ -515,7 +555,7 @@ user:
515 no: "Non" 555 no: "Non"
516 create_new_one: "Créer un nouvel utilisateur" 556 create_new_one: "Créer un nouvel utilisateur"
517 form: 557 form:
518 username_label: "Nom d’utilisateur" 558 username_label: "Identifiant (ne peut être changé)"
519 name_label: "Nom" 559 name_label: "Nom"
520 password_label: "Mot de passe" 560 password_label: "Mot de passe"
521 repeat_new_password_label: "Confirmez votre nouveau mot de passe" 561 repeat_new_password_label: "Confirmez votre nouveau mot de passe"
@@ -524,12 +564,14 @@ user:
524 enabled_label: "Activé" 564 enabled_label: "Activé"
525 last_login_label: "Dernière connexion" 565 last_login_label: "Dernière connexion"
526 twofactor_label: "Double authentification" 566 twofactor_label: "Double authentification"
567 twofactor_email_label: Double authentification par email
568 twofactor_google_label: Double authentification par OTP app
527 save: "Sauvegarder" 569 save: "Sauvegarder"
528 delete: "Supprimer" 570 delete: "Supprimer"
529 delete_confirm: "Êtes-vous sûr ?" 571 delete_confirm: "Êtes-vous sûr ?"
530 back_to_list: "Revenir à la liste" 572 back_to_list: "Revenir à la liste"
531 search: 573 search:
532 placeholder: "Filtrer par nom d’utilisateur ou email" 574 placeholder: "Filtrer par identifiant ou email"
533 575
534site_credential: 576site_credential:
535 page_title: Gestion des accès aux sites 577 page_title: Gestion des accès aux sites
@@ -544,7 +586,7 @@ site_credential:
544 create_new_one: Créer un nouvel accès à un site 586 create_new_one: Créer un nouvel accès à un site
545 form: 587 form:
546 username_label: 'Identifiant' 588 username_label: 'Identifiant'
547 host_label: 'Domaine' 589 host_label: 'Domaine (subdomain.example.org, .example.org, etc.)'
548 password_label: 'Mot de passe' 590 password_label: 'Mot de passe'
549 save: "Sauvegarder" 591 save: "Sauvegarder"
550 delete: "Supprimer" 592 delete: "Supprimer"
@@ -561,14 +603,18 @@ flashes:
561 password_updated: "Votre mot de passe a bien été mis à jour" 603 password_updated: "Votre mot de passe a bien été mis à jour"
562 password_not_updated_demo: "En démo, vous ne pouvez pas changer le mot de passe de cet utilisateur." 604 password_not_updated_demo: "En démo, vous ne pouvez pas changer le mot de passe de cet utilisateur."
563 user_updated: "Vos informations personnelles ont bien été mises à jour" 605 user_updated: "Vos informations personnelles ont bien été mises à jour"
564 rss_updated: "La configuration des flux RSS a bien été mise à jour" 606 feed_updated: "La configuration des flux a bien été mise à jour"
565 tagging_rules_updated: "Règles mises à jour" 607 tagging_rules_updated: "Règles mises à jour"
566 tagging_rules_deleted: "Règle supprimée" 608 tagging_rules_deleted: "Règle supprimée"
567 rss_token_updated: "Jeton RSS mis à jour" 609 feed_token_updated: "Jeton des flux mis à jour"
610 feed_token_revoked: 'Jeton des flux supprimé'
568 annotations_reset: "Annotations supprimées" 611 annotations_reset: "Annotations supprimées"
569 tags_reset: "Tags supprimés" 612 tags_reset: "Tags supprimés"
570 entries_reset: "Articles supprimés" 613 entries_reset: "Articles supprimés"
571 archived_reset: "Articles archivés supprimés" 614 archived_reset: "Articles archivés supprimés"
615 otp_enabled: "Authentification à double-facteur activée"
616 tagging_rules_imported: Règles bien importées
617 tagging_rules_not_imported: Impossible d'importer les règles
572 entry: 618 entry:
573 notice: 619 notice:
574 entry_already_saved: "Article déjà sauvegardé le %date%" 620 entry_already_saved: "Article déjà sauvegardé le %date%"
@@ -582,9 +628,11 @@ flashes:
582 entry_starred: "Article ajouté dans les favoris" 628 entry_starred: "Article ajouté dans les favoris"
583 entry_unstarred: "Article retiré des favoris" 629 entry_unstarred: "Article retiré des favoris"
584 entry_deleted: "Article supprimé" 630 entry_deleted: "Article supprimé"
631 no_random_entry: "Aucun article correspond aux critères n'a été trouvé"
585 tag: 632 tag:
586 notice: 633 notice:
587 tag_added: "Tag ajouté" 634 tag_added: "Tag ajouté"
635 tag_renamed: "Tag renommé"
588 import: 636 import:
589 notice: 637 notice:
590 failed: "L’import a échoué, veuillez ré-essayer" 638 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 83b3edcd..8cee3e52 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.it.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.it.yml
@@ -33,10 +33,12 @@ menu:
33 back_to_unread: 'Torna ai contenuti non letti' 33 back_to_unread: 'Torna ai contenuti non letti'
34 users_management: 'Gestione utenti' 34 users_management: 'Gestione utenti'
35 site_credentials: 'Credenziali sito' 35 site_credentials: 'Credenziali sito'
36 quickstart: "Introduzione"
36 top: 37 top:
37 add_new_entry: 'Aggiungi un nuovo contenuto' 38 add_new_entry: 'Aggiungi un nuovo contenuto'
38 search: 'Cerca' 39 search: 'Cerca'
39 filter_entries: 'Filtra contenuti' 40 filter_entries: 'Filtra contenuti'
41 # random_entry: Jump to a random entry from that list
40 export: 'Esporta' 42 export: 'Esporta'
41 search_form: 43 search_form:
42 input_label: 'Inserisci qui la tua ricerca' 44 input_label: 'Inserisci qui la tua ricerca'
@@ -53,11 +55,12 @@ config:
53 page_title: 'Configurazione' 55 page_title: 'Configurazione'
54 tab_menu: 56 tab_menu:
55 settings: 'Impostazioni' 57 settings: 'Impostazioni'
56 rss: 'RSS' 58 feed: 'RSS'
57 user_info: 'Informazioni utente' 59 user_info: 'Informazioni utente'
58 password: 'Password' 60 password: 'Password'
59 rules: 'Regole di etichettatura' 61 rules: 'Regole di etichettatura'
60 new_user: 'Aggiungi utente' 62 new_user: 'Aggiungi utente'
63 reset: 'Area di reset'
61 form: 64 form:
62 save: 'Salva' 65 save: 'Salva'
63 form_settings: 66 form_settings:
@@ -65,12 +68,8 @@ config:
65 items_per_page_label: 'Elementi per pagina' 68 items_per_page_label: 'Elementi per pagina'
66 language_label: 'Lingua' 69 language_label: 'Lingua'
67 reading_speed: 70 reading_speed:
68 label: 'Velocità di lettura' 71 label: 'Velocità di lettura (parole al minuto)'
69 help_message: 'Puoi utilizzare degli strumenti online per valutare la tua velocità di lettura:' 72 help_message: 'Puoi utilizzare degli strumenti online per valutare la tua velocità di lettura:'
70 100_word: 'Leggo ~100 parole al minuto'
71 200_word: 'Leggo ~200 parole al minuto'
72 300_word: 'Leggo ~300 parole al minuto'
73 400_word: 'Leggo ~400 parole al minuto'
74 action_mark_as_read: 73 action_mark_as_read:
75 label: "Dove vuoi essere reindirizzato dopo aver segnato l'articolo come già letto?" 74 label: "Dove vuoi essere reindirizzato dopo aver segnato l'articolo come già letto?"
76 redirect_homepage: 'Alla homepage' 75 redirect_homepage: 'Alla homepage'
@@ -83,25 +82,35 @@ 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." 82 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." 83 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." 84 help_pocket_consumer_key: "Richiesta per importare da Pocket. La puoi creare nel tuo account Pocket."
86 form_rss: 85 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.' 86 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' 87 token_label: 'Token RSS'
89 no_token: 'Nessun token' 88 no_token: 'Nessun token'
90 token_create: 'Crea il tuo token' 89 token_create: 'Crea il tuo token'
91 token_reset: 'Rigenera il tuo token' 90 token_reset: 'Rigenera il tuo token'
92 rss_links: 'Collegamenti RSS' 91 # token_revoke: 'Revoke the token'
93 rss_link: 92 feed_links: 'Collegamenti RSS'
93 feed_link:
94 unread: 'Non letti' 94 unread: 'Non letti'
95 starred: 'Preferiti' 95 starred: 'Preferiti'
96 archive: 'Archiviati' 96 archive: 'Archiviati'
97 # all: 'All' 97 # all: 'All'
98 rss_limit: 'Numero di elementi nel feed' 98 feed_limit: 'Numero di elementi nel feed'
99 form_user: 99 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" 100 # 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 # login_label: 'Login (can not be changed)'
101 name_label: 'Nome' 102 name_label: 'Nome'
102 email_label: 'E-mail' 103 email_label: 'E-mail'
103 twoFactorAuthentication_label: 'Autenticazione a due fattori' 104 two_factor:
104 help_twoFactorAuthentication: "Se abiliti l'autenticazione a due fattori, ogni volta che vorrai connetterti a wallabag, riceverai un codice via E-mail." 105 # emailTwoFactor_label: 'Using email (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: 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.
@@ -127,6 +136,15 @@ config:
127 edit_rule_label: 'modifica' 136 edit_rule_label: 'modifica'
128 rule_label: 'Regola' 137 rule_label: 'Regola'
129 tags_label: 'Etichetta' 138 tags_label: 'Etichetta'
139 # card:
140 # new_tagging_rule: Create a tagging rule
141 # import_tagging_rules: Import tagging rules
142 # import_tagging_rules_detail: You have to select the JSON file you previously exported.
143 # export_tagging_rules: Export tagging rules
144 # export_tagging_rules_detail: This will download a JSON file that you can use to import tagging rules elsewhere or to backup them.
145 # file_label: JSON file
146 # import_submit: Import
147 # export: Export
130 faq: 148 faq:
131 title: 'FAQ' 149 title: 'FAQ'
132 tagging_rules_definition_title: 'Cosa significa « regole di etichettatura » ?' 150 tagging_rules_definition_title: 'Cosa significa « regole di etichettatura » ?'
@@ -159,6 +177,15 @@ config:
159 and: "Una regola E un'altra" 177 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>' 178 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>' 179 # notmatches: 'Tests that a <i>subject</i> doesn''t match match a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
180 otp:
181 # page_title: Two-factor authentication
182 # app:
183 # 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.
184 # two_factor_code_description_2: 'You can scan that QR Code with your app:'
185 # 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:'
186 # two_factor_code_description_4: 'Test an OTP code from your configured app:'
187 # cancel: Cancel
188 # enable: Enable
162 189
163entry: 190entry:
164 default_title: "Titolo del contenuto" 191 default_title: "Titolo del contenuto"
@@ -237,7 +264,7 @@ entry:
237 # provided_by: 'Provided by' 264 # provided_by: 'Provided by'
238 new: 265 new:
239 page_title: 'Salva un nuovo contenuto' 266 page_title: 'Salva un nuovo contenuto'
240 placeholder: 'http://website.com' 267 placeholder: 'https://website.it'
241 form_new: 268 form_new:
242 url_label: Url 269 url_label: Url
243 search: 270 search:
@@ -253,6 +280,11 @@ entry:
253 confirm: 280 confirm:
254 delete: "Vuoi veramente rimuovere quell'articolo?" 281 delete: "Vuoi veramente rimuovere quell'articolo?"
255 delete_tag: "Vuoi veramente rimuovere quell'etichetta da quell'articolo?" 282 delete_tag: "Vuoi veramente rimuovere quell'etichetta da quell'articolo?"
283 metadata:
284 # reading_time: "Estimated reading time"
285 # reading_time_minutes_short: "%readingTime% min"
286 # address: "Address"
287 # added_on: "Added on"
256 288
257about: 289about:
258 page_title: 'A proposito' 290 page_title: 'A proposito'
@@ -348,7 +380,7 @@ quickstart:
348 title: "Configura l'applicazione" 380 title: "Configura l'applicazione"
349 description: "Per avere un'applicazione che ti soddisfi, dai un'occhiata alla configurazione di wallabag." 381 description: "Per avere un'applicazione che ti soddisfi, dai un'occhiata alla configurazione di wallabag."
350 language: 'Cambia lingua e design' 382 language: 'Cambia lingua e design'
351 rss: 'Abilita i feed RSS' 383 feed: 'Abilita i feed RSS'
352 tagging_rules: 'Scrivi delle regole per taggare automaticamente i contenuti' 384 tagging_rules: 'Scrivi delle regole per taggare automaticamente i contenuti'
353 admin: 385 admin:
354 title: 'Amministrazione' 386 title: 'Amministrazione'
@@ -396,12 +428,16 @@ tag:
396 list: 428 list:
397 number_on_the_page: "{0} Non ci sono etichette.|{1} C'è un'etichetta.|]1,Inf[ ci sono %count% etichette." 429 number_on_the_page: "{0} Non ci sono etichette.|{1} C'è un'etichetta.|]1,Inf[ ci sono %count% etichette."
398 see_untagged_entries: 'Vedi articoli non etichettati' 430 see_untagged_entries: 'Vedi articoli non etichettati'
431 # no_untagged_entries: 'There are no untagged entries.'
399 new: 432 new:
400 add: 'Aggiungi' 433 add: 'Aggiungi'
401 placeholder: 'Puoi aggiungere varie etichette, separate da una virgola.' 434 placeholder: 'Puoi aggiungere varie etichette, separate da una virgola.'
435 rename:
436 # placeholder: 'You can update tag name.'
402 437
403# export: 438# export:
404# 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>' 439# 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>'
440# unknown: 'Unknown'
405 441
406import: 442import:
407 page_title: 'Importa' 443 page_title: 'Importa'
@@ -429,6 +465,9 @@ import:
429 wallabag_v2: 465 wallabag_v2:
430 page_title: 'Importa da > Wallabag v2' 466 page_title: 'Importa da > Wallabag v2'
431 description: 'Questo importatore copierà tutti i tuoi dati da un wallabag v2. Vai in "Tutti i contenuti", e, nella barra laterale di esportazione, clicca su "JSON". Otterrai un file "Tutti i contenuti.json' 467 description: 'Questo importatore copierà tutti i tuoi dati da un wallabag v2. Vai in "Tutti i contenuti", e, nella barra laterale di esportazione, clicca su "JSON". Otterrai un file "Tutti i contenuti.json'
468 # elcurator:
469 # page_title: 'Import > elCurator'
470 # description: 'This importer will import all your elCurator articles. Go to your preferences in your elCurator account and then, export your content. You will have a JSON file.'
432 readability: 471 readability:
433 page_title: 'Importa da > Readability' 472 page_title: 'Importa da > Readability'
434 description: 'Questo importatore copierà tutti i tuoi articoli da Readability. Nella pagina strumenti (https://www.readability.com/tools/), clicca su "Export your data" nella sezione "Data Export". Riceverai una E-mail per scaricare un file json (che tuttavia non termina con .json).' 473 description: 'Questo importatore copierà tutti i tuoi articoli da Readability. Nella pagina strumenti (https://www.readability.com/tools/), clicca su "Export your data" nella sezione "Data Export". Riceverai una E-mail per scaricare un file json (che tuttavia non termina con .json).'
@@ -482,6 +521,7 @@ developer:
482 redirect_uris_label: 'Redirect URI' 521 redirect_uris_label: 'Redirect URI'
483 save_label: 'Crea un nuovo client' 522 save_label: 'Crea un nuovo client'
484 action_back: 'Indietro' 523 action_back: 'Indietro'
524 # copy_to_clipboard: Copy
485 client_parameter: 525 client_parameter:
486 page_title: 'Gestione client API > Parametri Client' 526 page_title: 'Gestione client API > Parametri Client'
487 page_description: 'Questi sono i tuoi parametri del client.' 527 page_description: 'Questi sono i tuoi parametri del client.'
@@ -523,7 +563,8 @@ user:
523 email_label: 'E-mail' 563 email_label: 'E-mail'
524 enabled_label: 'Abilitato' 564 enabled_label: 'Abilitato'
525 last_login_label: 'Ultima connessione' 565 last_login_label: 'Ultima connessione'
526 twofactor_label: Autenticazione a due fattori 566 # twofactor_email_label: Two factor authentication by email
567 # twofactor_google_label: Two factor authentication by OTP app
527 save: Salva 568 save: Salva
528 delete: Cancella 569 delete: Cancella
529 delete_confirm: Sei sicuro? 570 delete_confirm: Sei sicuro?
@@ -544,7 +585,7 @@ site_credential:
544 # create_new_one: Create a new credential 585 # create_new_one: Create a new credential
545 # form: 586 # form:
546 # username_label: 'Username' 587 # username_label: 'Username'
547 # host_label: 'Host' 588 # host_label: 'Host (subdomain.example.org, .example.org, etc.)'
548 # password_label: 'Password' 589 # password_label: 'Password'
549 # save: Save 590 # save: Save
550 # delete: Delete 591 # delete: Delete
@@ -561,14 +602,18 @@ flashes:
561 password_updated: 'Password aggiornata' 602 password_updated: 'Password aggiornata'
562 password_not_updated_demo: "In modalità demo, non puoi cambiare la password dell'utente." 603 password_not_updated_demo: "In modalità demo, non puoi cambiare la password dell'utente."
563 user_updated: 'Informazioni aggiornate' 604 user_updated: 'Informazioni aggiornate'
564 rss_updated: 'Informazioni RSS aggiornate' 605 feed_updated: 'Informazioni RSS aggiornate'
565 tagging_rules_updated: 'Regole di etichettatura aggiornate' 606 tagging_rules_updated: 'Regole di etichettatura aggiornate'
566 tagging_rules_deleted: 'Regola di etichettatura eliminate' 607 tagging_rules_deleted: 'Regola di etichettatura eliminate'
567 rss_token_updated: 'RSS token aggiornato' 608 feed_token_updated: 'RSS token aggiornato'
609 # feed_token_revoked: 'RSS token revoked'
568 annotations_reset: Reset annotazioni 610 annotations_reset: Reset annotazioni
569 tags_reset: Reset etichette 611 tags_reset: Reset etichette
570 entries_reset: Reset articoli 612 entries_reset: Reset articoli
571 # archived_reset: Archived entries deleted 613 # archived_reset: Archived entries deleted
614 # otp_enabled: Two-factor authentication enabled
615 # tagging_rules_imported: Tagging rules imported
616 # tagging_rules_not_imported: Error while importing tagging rules
572 entry: 617 entry:
573 notice: 618 notice:
574 entry_already_saved: 'Contenuto già salvato in data %date%' 619 entry_already_saved: 'Contenuto già salvato in data %date%'
@@ -582,9 +627,11 @@ flashes:
582 entry_starred: 'Contenuto segnato come preferito' 627 entry_starred: 'Contenuto segnato come preferito'
583 entry_unstarred: 'Contenuto rimosso dai preferiti' 628 entry_unstarred: 'Contenuto rimosso dai preferiti'
584 entry_deleted: 'Contenuto eliminato' 629 entry_deleted: 'Contenuto eliminato'
630 # no_random_entry: 'No article with these criterias was found'
585 tag: 631 tag:
586 notice: 632 notice:
587 tag_added: 'Etichetta aggiunta' 633 tag_added: 'Etichetta aggiunta'
634 # tag_renamed: 'Tag renamed'
588 import: 635 import:
589 notice: 636 notice:
590 failed: 'Importazione fallita, riprova.' 637 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 95bc9560..052582ab 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml
@@ -33,10 +33,12 @@ menu:
33 back_to_unread: 'Tornar als articles pas legits' 33 back_to_unread: 'Tornar als articles pas legits'
34 users_management: 'Gestion dels utilizaires' 34 users_management: 'Gestion dels utilizaires'
35 site_credentials: 'Identificants del site' 35 site_credentials: 'Identificants del site'
36 quickstart: "Per ben començar"
36 top: 37 top:
37 add_new_entry: 'Enregistrar un novèl article' 38 add_new_entry: 'Enregistrar un novèl article'
38 search: 'Cercar' 39 search: 'Cercar'
39 filter_entries: 'Filtrar los articles' 40 filter_entries: 'Filtrar los articles'
41 random_entry: "Sautar a un article a l'azard"
40 export: 'Exportar' 42 export: 'Exportar'
41 search_form: 43 search_form:
42 input_label: 'Picatz vòstre mot-clau a cercar aquí' 44 input_label: 'Picatz vòstre mot-clau a cercar aquí'
@@ -53,11 +55,12 @@ config:
53 page_title: 'Configuracion' 55 page_title: 'Configuracion'
54 tab_menu: 56 tab_menu:
55 settings: 'Paramètres' 57 settings: 'Paramètres'
56 rss: 'RSS' 58 feed: 'RSS'
57 user_info: 'Mon compte' 59 user_info: 'Mon compte'
58 password: 'Senhal' 60 password: 'Senhal'
59 rules: "Règlas d'etiquetas automaticas" 61 rules: "Règlas d'etiquetas automaticas"
60 new_user: 'Crear un compte' 62 new_user: 'Crear un compte'
63 reset: 'Zòna de reïnicializacion'
61 form: 64 form:
62 save: 'Enregistrar' 65 save: 'Enregistrar'
63 form_settings: 66 form_settings:
@@ -65,12 +68,8 @@ config:
65 items_per_page_label: "Nombre d'articles per pagina" 68 items_per_page_label: "Nombre d'articles per pagina"
66 language_label: 'Lenga' 69 language_label: 'Lenga'
67 reading_speed: 70 reading_speed:
68 label: 'Velocitat de lectura' 71 label: 'Velocitat de lectura (mots per minuta)'
69 help_message: 'Podètz utilizar una aisina en linha per estimar vòstra velocitat de lectura :' 72 help_message: 'Podètz utilizar una aisina en linha per estimar vòstra velocitat de lectura :'
70 100_word: "Legissi a l'entorn de 100 mots per minuta"
71 200_word: "Legissi a l'entorn de 200 mots per minuta"
72 300_word: "Legissi a l'entorn de 300 mots per minuta"
73 400_word: "Legissi a l'entorn de 400 mots per minuta"
74 action_mark_as_read: 73 action_mark_as_read:
75 label: 'Ont volètz èsser menat aprèp aver marcat un article coma legit ?' 74 label: 'Ont volètz èsser menat aprèp aver marcat un article coma legit ?'
76 redirect_homepage: "A la pagina d’acuèlh" 75 redirect_homepage: "A la pagina d’acuèlh"
@@ -83,25 +82,35 @@ 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." 82 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." 83 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." 84 help_pocket_consumer_key: "Requesida per l'importacion de Pocket. Podètz la crear dins vòstre compte Pocket."
86 form_rss: 85 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." 86 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' 87 token_label: 'Geton RSS'
89 no_token: 'Pas cap de geton generat' 88 no_token: 'Pas cap de geton generat'
90 token_create: 'Creatz vòstre geton' 89 token_create: 'Creatz vòstre geton'
91 token_reset: 'Reïnicializatz vòstre geton' 90 token_reset: 'Reïnicializatz vòstre geton'
92 rss_links: 'URLs de vòstres fluxes RSS' 91 oken_revoke: 'Revocar lo geton'
93 rss_link: 92 feed_links: 'URLs de vòstres fluxes RSS'
93 feed_link:
94 unread: 'Pas legits' 94 unread: 'Pas legits'
95 starred: 'Favorits' 95 starred: 'Favorits'
96 archive: 'Legits' 96 archive: 'Legits'
97 all: 'Totes' 97 all: 'Totes'
98 rss_limit: "Nombre d'articles dins un flux RSS" 98 feed_limit: "Nombre d'articles dins un flux"
99 form_user: 99 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." 100 two_factor_description: "L’activacion de l’autentificacion en dos temps indica que recebretz un còdi per corrièl amb un còdi O que vos cal utilizar una aplicacion OTP (coma Google Authenticator, Authy o FreeOTP) per obténer un còdi a usatge unic cada còp qu’i a una connexion pas fisabla. Podètz pas causir las doas opcions."
101 login_label: 'Identificant (se pòt pas cambiar)'
101 name_label: 'Nom' 102 name_label: 'Nom'
102 email_label: 'Adreça de corrièl' 103 email_label: 'Adreça de corrièl'
103 twoFactorAuthentication_label: 'Dobla autentificacion' 104 two_factor:
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." 105 emailTwoFactor_label: 'En utilizar lo corrièl (recebre un còdi per corrièl)'
106 googleTwoFactor_label: 'En utilizar una aplicacion OTP (Dobrir l’aplicacion, coma Google Authenticator, Authy o FreeOTP, per obténer un còdi a usatge unic)'
107 table_method: Metòde
108 table_state: Estat
109 table_action: Accion
110 state_enabled: Activada
111 state_disabled: Desactivada
112 action_email: Utilizar lo corrièl
113 action_app: Utilizar una aplicacion OTP
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.
@@ -127,6 +136,15 @@ config:
127 edit_rule_label: 'modificar' 136 edit_rule_label: 'modificar'
128 rule_label: 'Règla' 137 rule_label: 'Règla'
129 tags_label: 'Etiquetas' 138 tags_label: 'Etiquetas'
139 card:
140 new_tagging_rule: Crear una règla d’etiquetatge
141 import_tagging_rules: Importar de règlas d’etiquetatge
142 import_tagging_rules_detail: Vos cal causir un fichièr JSON qu’importèretz per abans.
143 export_tagging_rules: Exportar las règlas d’etiquetatge
144 export_tagging_rules_detail: Telecargarà un fichièr JSON que podètz utilizar per importar las règlas d’etiquetatge endacòm mai o per las salvagardar.
145 file_label: fichièr JSON
146 import_submit: Importar
147 export: Exportar
130 faq: 148 faq:
131 title: 'FAQ' 149 title: 'FAQ'
132 tagging_rules_definition_title: "Qué significa las règlas d'etiquetas automaticas ?" 150 tagging_rules_definition_title: "Qué significa las règlas d'etiquetas automaticas ?"
@@ -159,6 +177,15 @@ config:
159 and: "Una règla E l'autra" 177 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>' 178 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>' 179 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>'
180 otp:
181 page_title: Autentificacion en dos temps
182 app:
183 two_factor_code_description_1: Avètz pas qu’activat l’autentificacion en dos temps, dobrissètz l’aplicacion OTP e utilizatz aqueste còdi per obténer un senhal unic. Apareisserà aprèp un recargament de pagina.
184 two_factor_code_description_2: 'Podètz numerizar aqueste còdi QR amb l’aplicacion :'
185 two_factor_code_description_3: 'Amai, enregistratz aquestes còdis de recuperacion dins un lòc segur, los podètz utilizar se per cas perdatz l’accès a l’aplicacion OTP :'
186 two_factor_code_description_4: 'Ensajatz un còdi de vòstra aplicacion configurada app :'
187 cancel: Anullar
188 enable: Activar
162 189
163entry: 190entry:
164 default_title: "Títol de l'article" 191 default_title: "Títol de l'article"
@@ -186,7 +213,7 @@ entry:
186 export_title: 'Exportar' 213 export_title: 'Exportar'
187 filters: 214 filters:
188 title: 'Filtres' 215 title: 'Filtres'
189 status_label: 'Estatus' 216 status_label: 'Estat'
190 archived_label: 'Legits' 217 archived_label: 'Legits'
191 starred_label: 'Favorits' 218 starred_label: 'Favorits'
192 unread_label: 'Pas legits' 219 unread_label: 'Pas legits'
@@ -195,7 +222,7 @@ entry:
195 is_public_label: 'Ten un ligam public' 222 is_public_label: 'Ten un ligam public'
196 is_public_help: 'Ligam public' 223 is_public_help: 'Ligam public'
197 language_label: 'Lenga' 224 language_label: 'Lenga'
198 http_status_label: 'Estatut HTTP' 225 http_status_label: 'Estat HTTP'
199 reading_time: 226 reading_time:
200 label: 'Durada de lectura en minutas' 227 label: 'Durada de lectura en minutas'
201 from: 'de' 228 from: 'de'
@@ -237,7 +264,7 @@ entry:
237 provided_by: 'Provesit per' 264 provided_by: 'Provesit per'
238 new: 265 new:
239 page_title: 'Enregistrar un novèl article' 266 page_title: 'Enregistrar un novèl article'
240 placeholder: 'http://website.com' 267 placeholder: 'https://website.cat'
241 form_new: 268 form_new:
242 url_label: Url 269 url_label: Url
243 search: 270 search:
@@ -253,6 +280,11 @@ entry:
253 confirm: 280 confirm:
254 delete: "Sètz segur de voler suprimir aqueste article ?" 281 delete: "Sètz segur de voler suprimir aqueste article ?"
255 delete_tag: "Sètz segur de voler levar aquesta etiqueta de l'article ?" 282 delete_tag: "Sètz segur de voler levar aquesta etiqueta de l'article ?"
283 metadata:
284 reading_time: "Temps de lectura estimat"
285 reading_time_minutes_short: "%readingTime% min"
286 address: "Adreça"
287 added_on: "Ajustat a"
256 288
257about: 289about:
258 page_title: 'A prepaus' 290 page_title: 'A prepaus'
@@ -348,7 +380,7 @@ quickstart:
348 title: "Configuratz l'aplicacion" 380 title: "Configuratz l'aplicacion"
349 description: "Per fin d'aver una aplicacion que vos va ben, anatz veire la configuracion de wallabag." 381 description: "Per fin d'aver una aplicacion que vos va ben, anatz veire la configuracion de wallabag."
350 language: "Cambiatz la lenga e l'estil de l'aplicacion" 382 language: "Cambiatz la lenga e l'estil de l'aplicacion"
351 rss: 'Activatz los fluxes RSS' 383 feed: 'Activatz los fluxes RSS'
352 tagging_rules: 'Escrivètz de règlas per classar automaticament vòstres articles' 384 tagging_rules: 'Escrivètz de règlas per classar automaticament vòstres articles'
353 admin: 385 admin:
354 title: 'Administracion' 386 title: 'Administracion'
@@ -396,12 +428,16 @@ tag:
396 list: 428 list:
397 number_on_the_page: "{0} I a pas cap d'etiquetas.|{1} I a una etiqueta.|]1,Inf[ I a %count% etiquetas." 429 number_on_the_page: "{0} I a pas cap d'etiquetas.|{1} I a una etiqueta.|]1,Inf[ I a %count% etiquetas."
398 see_untagged_entries: "Afichar las entradas sens etiquetas" 430 see_untagged_entries: "Afichar las entradas sens etiquetas"
431 no_untagged_entries: 'I a pas cap d’article pas etiquetat.'
399 new: 432 new:
400 add: 'Ajustar' 433 add: 'Ajustar'
401 placeholder: "Podètz ajustar mai qu'una etiqueta, separadas per de virgula." 434 placeholder: "Podètz ajustar mai qu'una etiqueta, separadas per de virgula."
435 rename:
436 placeholder: 'Podètz actualizar lo nom de l’etiqueta.'
402 437
403export: 438export:
404 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>' 439 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>'
440 unknown: 'Desconegut'
405 441
406import: 442import:
407 page_title: 'Importar' 443 page_title: 'Importar'
@@ -429,6 +465,9 @@ import:
429 wallabag_v2: 465 wallabag_v2:
430 page_title: 'Importar > Wallabag v2' 466 page_title: 'Importar > Wallabag v2'
431 description: "Aquesta aisina importarà totas vòstras donadas d'una instància mai de wallabag v2. Anatz dins totes vòstres articles, puèi, sus la barra laterala, clicatz sus \"JSON\". Traparetz un fichièr \"All articles.json\"." 467 description: "Aquesta aisina importarà totas vòstras donadas d'una instància mai de wallabag v2. Anatz dins totes vòstres articles, puèi, sus la barra laterala, clicatz sus \"JSON\". Traparetz un fichièr \"All articles.json\"."
468 # elcurator:
469 # page_title: 'Import > elCurator'
470 # description: 'This importer will import all your elCurator articles. Go to your preferences in your elCurator account and then, export your content. You will have a JSON file.'
432 readability: 471 readability:
433 page_title: 'Importar > Readability' 472 page_title: 'Importar > Readability'
434 description: "Aquesta aisina importarà totas vòstres articles de Readability. Sus la pagina de l'aisina (https://www.readability.com/tools/), clicatz sus \"Export your data\" dins la seccion \"Data Export\". Recebretz un corrièl per telecargar un json (qu'acaba pas amb un .json de fach)." 473 description: "Aquesta aisina importarà totas vòstres articles de Readability. Sus la pagina de l'aisina (https://www.readability.com/tools/), clicatz sus \"Export your data\" dins la seccion \"Data Export\". Recebretz un corrièl per telecargar un json (qu'acaba pas amb un .json de fach)."
@@ -482,6 +521,7 @@ developer:
482 redirect_uris_label: 'URLs de redireccion' 521 redirect_uris_label: 'URLs de redireccion'
483 save_label: 'Crear un novèl client' 522 save_label: 'Crear un novèl client'
484 action_back: 'Retorn' 523 action_back: 'Retorn'
524 copy_to_clipboard: Copiar
485 client_parameter: 525 client_parameter:
486 page_title: 'Gestion dels clients API > Los paramètres de vòstre client' 526 page_title: 'Gestion dels clients API > Los paramètres de vòstre client'
487 page_description: 'Vaquí los paramètres de vòstre client.' 527 page_description: 'Vaquí los paramètres de vòstre client.'
@@ -523,7 +563,8 @@ user:
523 email_label: 'Adreça de corrièl' 563 email_label: 'Adreça de corrièl'
524 enabled_label: 'Actiu' 564 enabled_label: 'Actiu'
525 last_login_label: 'Darrièra connexion' 565 last_login_label: 'Darrièra connexion'
526 twofactor_label: 'Autentificacion doble-factor' 566 twofactor_email_label: Autentificacion en dos temps per corrièl
567 twofactor_google_label: Autentificacion en dos temps per aplicacion OTP
527 save: 'Enregistrar' 568 save: 'Enregistrar'
528 delete: 'Suprimir' 569 delete: 'Suprimir'
529 delete_confirm: 'Sètz segur ?' 570 delete_confirm: 'Sètz segur ?'
@@ -544,7 +585,7 @@ site_credential:
544 create_new_one: Crear un novèl identificant 585 create_new_one: Crear un novèl identificant
545 form: 586 form:
546 username_label: "Nom d'utilizaire" 587 username_label: "Nom d'utilizaire"
547 host_label: 'Òste' 588 host_label: 'Òste (subdomain.example.org, .example.org, etc.)'
548 password_label: 'Senhal' 589 password_label: 'Senhal'
549 save: 'Enregistrar' 590 save: 'Enregistrar'
550 delete: 'Suprimir' 591 delete: 'Suprimir'
@@ -561,14 +602,18 @@ flashes:
561 password_updated: 'Vòstre senhal es ben estat mes a jorn' 602 password_updated: 'Vòstre senhal es ben estat mes a jorn'
562 password_not_updated_demo: "En demostracion, podètz pas cambiar lo senhal d'aqueste utilizaire." 603 password_not_updated_demo: "En demostracion, podètz pas cambiar lo senhal d'aqueste utilizaire."
563 user_updated: 'Vòstres informacions personnelas son ben estadas mesas a jorn' 604 user_updated: 'Vòstres informacions personnelas son ben estadas mesas a jorn'
564 rss_updated: 'La configuracion dels fluxes RSS es ben estada mesa a jorn' 605 feed_updated: 'La configuracion dels fluxes RSS es ben estada mesa a jorn'
565 tagging_rules_updated: 'Règlas misa a jorn' 606 tagging_rules_updated: 'Règlas misa a jorn'
566 tagging_rules_deleted: 'Règla suprimida' 607 tagging_rules_deleted: 'Règla suprimida'
567 rss_token_updated: 'Geton RSS mes a jorn' 608 feed_token_updated: 'Geton RSS mes a jorn'
609 feed_token_revoked: 'Geton RSS revocat'
568 annotations_reset: Anotacions levadas 610 annotations_reset: Anotacions levadas
569 tags_reset: Etiquetas levadas 611 tags_reset: Etiquetas levadas
570 entries_reset: Articles levats 612 entries_reset: Articles levats
571 archived_reset: Articles archivat suprimits 613 archived_reset: Articles archivat suprimits
614 otp_enabled: Autentificacion en dos temps activada
615 tagging_rules_imported: Règlas d’etiquetatge importadas
616 tagging_rules_not_imported: Error en important las règlas d’etiquetatge
572 entry: 617 entry:
573 notice: 618 notice:
574 entry_already_saved: 'Article ja salvagardat lo %date%' 619 entry_already_saved: 'Article ja salvagardat lo %date%'
@@ -582,9 +627,11 @@ flashes:
582 entry_starred: 'Article ajustat dins los favorits' 627 entry_starred: 'Article ajustat dins los favorits'
583 entry_unstarred: 'Article quitat dels favorits' 628 entry_unstarred: 'Article quitat dels favorits'
584 entry_deleted: 'Article suprimit' 629 entry_deleted: 'Article suprimit'
630 no_random_entry: 'Cap d’article pas trobat amb aquestes critèris'
585 tag: 631 tag:
586 notice: 632 notice:
587 tag_added: 'Etiqueta ajustada' 633 tag_added: 'Etiqueta ajustada'
634 tag_renamed: 'Etiqueta renomenada'
588 import: 635 import:
589 notice: 636 notice:
590 failed: "L'importacion a fracassat, mercés de tornar ensajar." 637 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 a64e60b0..93e8f852 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml
@@ -33,10 +33,12 @@ menu:
33 back_to_unread: 'Powrót do nieprzeczytanych artykułów' 33 back_to_unread: 'Powrót do nieprzeczytanych artykułów'
34 users_management: 'Zarządzanie użytkownikami' 34 users_management: 'Zarządzanie użytkownikami'
35 site_credentials: 'Poświadczenia strony' 35 site_credentials: 'Poświadczenia strony'
36 quickstart: "Szybki start"
36 top: 37 top:
37 add_new_entry: 'Dodaj nowy wpis' 38 add_new_entry: 'Dodaj nowy wpis'
38 search: 'Szukaj' 39 search: 'Szukaj'
39 filter_entries: 'Filtruj wpisy' 40 filter_entries: 'Filtruj wpisy'
41 # random_entry: Jump to a random entry from that list
40 export: 'Eksportuj' 42 export: 'Eksportuj'
41 search_form: 43 search_form:
42 input_label: 'Wpisz swoje zapytanie tutaj' 44 input_label: 'Wpisz swoje zapytanie tutaj'
@@ -53,11 +55,12 @@ config:
53 page_title: 'Konfiguracja' 55 page_title: 'Konfiguracja'
54 tab_menu: 56 tab_menu:
55 settings: 'Ustawienia' 57 settings: 'Ustawienia'
56 rss: 'Kanał RSS' 58 feed: 'Kanał RSS'
57 user_info: 'Informacje o użytkowniku' 59 user_info: 'Informacje o użytkowniku'
58 password: 'Hasło' 60 password: 'Hasło'
59 rules: 'Zasady tagowania' 61 rules: 'Zasady tagowania'
60 new_user: 'Dodaj użytkownika' 62 new_user: 'Dodaj użytkownika'
63 reset: 'Reset'
61 form: 64 form:
62 save: 'Zapisz' 65 save: 'Zapisz'
63 form_settings: 66 form_settings:
@@ -65,12 +68,8 @@ config:
65 items_per_page_label: 'Ilość elementów na stronie' 68 items_per_page_label: 'Ilość elementów na stronie'
66 language_label: 'Język' 69 language_label: 'Język'
67 reading_speed: 70 reading_speed:
68 label: 'Prędkość czytania' 71 label: 'Prędkość czytania (słów na minutę)'
69 help_message: 'Możesz skorzystać z narzędzi online do określenia twojej prędkości czytania:' 72 help_message: 'Możesz skorzystać z narzędzi online do określenia twojej prędkości czytania:'
70 100_word: 'Czytam ~100 słów na minutę'
71 200_word: 'Czytam ~200 słów na minutę'
72 300_word: 'Czytam ~300 słów na minutę'
73 400_word: 'Czytam ~400 słów na minutę'
74 action_mark_as_read: 73 action_mark_as_read:
75 label: 'Gdzie zostaniesz przekierowany po oznaczeniu artukuły jako przeczytanego' 74 label: 'Gdzie zostaniesz przekierowany po oznaczeniu artukuły jako przeczytanego'
76 redirect_homepage: 'do strony głównej' 75 redirect_homepage: 'do strony głównej'
@@ -83,25 +82,35 @@ 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." 82 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." 83 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." 84 help_pocket_consumer_key: "Wymagane dla importu z Pocket. Możesz go stworzyć na swoim koncie Pocket."
86 form_rss: 85 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.‌' 86 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' 87 token_label: 'Token RSS'
89 no_token: 'Brak tokena' 88 no_token: 'Brak tokena'
90 token_create: 'Stwórz tokena' 89 token_create: 'Stwórz tokena'
91 token_reset: 'Zresetuj swojego tokena' 90 token_reset: 'Zresetuj swojego tokena'
92 rss_links: 'RSS links' 91 # token_revoke: 'Revoke the token'
93 rss_link: 92 feed_links: 'RSS links'
93 feed_link:
94 unread: 'Nieprzeczytane' 94 unread: 'Nieprzeczytane'
95 starred: 'Oznaczone gwiazdką' 95 starred: 'Oznaczone gwiazdką'
96 archive: 'Archiwum' 96 archive: 'Archiwum'
97 all: 'Wszystkie' 97 all: 'Wszystkie'
98 rss_limit: 'Link do RSS' 98 feed_limit: 'Link do RSS'
99 form_user: 99 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" 100 # 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 # login_label: 'Login (can not be changed)'
101 name_label: 'Nazwa' 102 name_label: 'Nazwa'
102 email_label: 'Adres email' 103 email_label: 'Adres email'
103 twoFactorAuthentication_label: 'Autoryzacja dwuetapowa' 104 two_factor:
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." 105 # emailTwoFactor_label: 'Using email (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: 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.
@@ -127,6 +136,15 @@ config:
127 edit_rule_label: 'edytuj' 136 edit_rule_label: 'edytuj'
128 rule_label: 'Reguła' 137 rule_label: 'Reguła'
129 tags_label: 'Tagi' 138 tags_label: 'Tagi'
139 # card:
140 # new_tagging_rule: Create a tagging rule
141 # import_tagging_rules: Import tagging rules
142 # import_tagging_rules_detail: You have to select the JSON file you previously exported.
143 # export_tagging_rules: Export tagging rules
144 # export_tagging_rules_detail: This will download a JSON file that you can use to import tagging rules elsewhere or to backup them.
145 # file_label: JSON file
146 # import_submit: Import
147 # export: Export
130 faq: 148 faq:
131 title: 'FAQ' 149 title: 'FAQ'
132 tagging_rules_definition_title: 'Co oznaczają « reguły tagowania » ?' 150 tagging_rules_definition_title: 'Co oznaczają « reguły tagowania » ?'
@@ -159,6 +177,15 @@ config:
159 and: 'Jedna reguła I inna' 177 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>' 178 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>' 179 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>'
180 otp:
181 # page_title: Two-factor authentication
182 # app:
183 # 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.
184 # two_factor_code_description_2: 'You can scan that QR Code with your app:'
185 # 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:'
186 # two_factor_code_description_4: 'Test an OTP code from your configured app:'
187 # cancel: Cancel
188 # enable: Enable
162 189
163entry: 190entry:
164 default_title: 'Tytuł wpisu' 191 default_title: 'Tytuł wpisu'
@@ -237,7 +264,7 @@ entry:
237 provided_by: 'Dostarczony przez' 264 provided_by: 'Dostarczony przez'
238 new: 265 new:
239 page_title: 'Zapisz nowy wpis' 266 page_title: 'Zapisz nowy wpis'
240 placeholder: 'http://website.com' 267 placeholder: 'https://website.pl'
241 form_new: 268 form_new:
242 url_label: Url 269 url_label: Url
243 search: 270 search:
@@ -253,6 +280,11 @@ entry:
253 confirm: 280 confirm:
254 delete: "Czy jesteś pewien, że chcesz usunąć ten artykuł?" 281 delete: "Czy jesteś pewien, że chcesz usunąć ten artykuł?"
255 delete_tag: "Czy jesteś pewien, że chcesz usunąć ten tag, z tego artykułu?" 282 delete_tag: "Czy jesteś pewien, że chcesz usunąć ten tag, z tego artykułu?"
283 metadata:
284 # reading_time: "Estimated reading time"
285 # reading_time_minutes_short: "%readingTime% min"
286 # address: "Address"
287 # added_on: "Added on"
256 288
257about: 289about:
258 page_title: 'O nas' 290 page_title: 'O nas'
@@ -348,7 +380,7 @@ quickstart:
348 title: 'Konfiguruj aplikację' 380 title: 'Konfiguruj aplikację'
349 description: 'W celu dopasowania aplikacji do swoich upodobań, zobacz konfigurację aplikacji' 381 description: 'W celu dopasowania aplikacji do swoich upodobań, zobacz konfigurację aplikacji'
350 language: 'Zmień język i wygląd' 382 language: 'Zmień język i wygląd'
351 rss: 'Włącz kanały RSS' 383 feed: 'Włącz kanały RSS'
352 tagging_rules: 'Napisz reguły pozwalające na automatyczne otagowanie twoich artykułów' 384 tagging_rules: 'Napisz reguły pozwalające na automatyczne otagowanie twoich artykułów'
353 admin: 385 admin:
354 title: 'Administracja' 386 title: 'Administracja'
@@ -396,12 +428,16 @@ tag:
396 list: 428 list:
397 number_on_the_page: '{0} Nie ma tagów.|{1} Jest jeden tag.|]1,Inf[ Są %count% tagi.' 429 number_on_the_page: '{0} Nie ma tagów.|{1} Jest jeden tag.|]1,Inf[ Są %count% tagi.'
398 see_untagged_entries: 'Zobacz nieotagowane wpisy' 430 see_untagged_entries: 'Zobacz nieotagowane wpisy'
431 # no_untagged_entries: 'There are no untagged entries.'
399 new: 432 new:
400 add: 'Dodaj' 433 add: 'Dodaj'
401 placeholder: 'Możesz dodać kilka tagów, oddzielając je przecinkami.' 434 placeholder: 'Możesz dodać kilka tagów, oddzielając je przecinkami.'
435 rename:
436 placeholder: 'Możesz zaktualizować nazwę taga.'
402 437
403export: 438export:
404 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>' 439 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>'
440 # unknown: 'Unknown'
405 441
406import: 442import:
407 page_title: 'Import' 443 page_title: 'Import'
@@ -429,6 +465,9 @@ import:
429 wallabag_v2: 465 wallabag_v2:
430 page_title: 'Import > Wallabag v2' 466 page_title: 'Import > Wallabag v2'
431 description: 'Ten importer, zaimportuje wszystkie twoje artykułu z wallabag v2. Idź do wszystkich artykułów, a następnie na panelu exportu kliknij na "JSON". Otrzymasz plik "All articles.json".' 467 description: 'Ten importer, zaimportuje wszystkie twoje artykułu z wallabag v2. Idź do wszystkich artykułów, a następnie na panelu exportu kliknij na "JSON". Otrzymasz plik "All articles.json".'
468 # elcurator:
469 # page_title: 'Import > elCurator'
470 # description: 'This importer will import all your elCurator articles. Go to your preferences in your elCurator account and then, export your content. You will have a JSON file.'
432 readability: 471 readability:
433 page_title: 'Import > Readability' 472 page_title: 'Import > Readability'
434 description: 'Ten importer, zaimportuje wszystkie twoje artykuły z Readability. Na stronie narzędzi (https://www.readability.com/tools/), kliknij na "Export your data" w sekcji "Data Export". Otrzymach email z plikiem JSON (plik nie będzie zawierał rozszerzenia .json).' 473 description: 'Ten importer, zaimportuje wszystkie twoje artykuły z Readability. Na stronie narzędzi (https://www.readability.com/tools/), kliknij na "Export your data" w sekcji "Data Export". Otrzymach email z plikiem JSON (plik nie będzie zawierał rozszerzenia .json).'
@@ -482,6 +521,7 @@ developer:
482 redirect_uris_label: 'Przekieruj adresy URI' 521 redirect_uris_label: 'Przekieruj adresy URI'
483 save_label: 'Stwórz nowego klienta' 522 save_label: 'Stwórz nowego klienta'
484 action_back: 'Cofnij' 523 action_back: 'Cofnij'
524 # copy_to_clipboard: Copy
485 client_parameter: 525 client_parameter:
486 page_title: 'Zarządzanie klientami API > Parametry klienta' 526 page_title: 'Zarządzanie klientami API > Parametry klienta'
487 page_description: 'Tutaj znajdują się parametry klienta.' 527 page_description: 'Tutaj znajdują się parametry klienta.'
@@ -523,7 +563,8 @@ user:
523 email_label: 'Adres email' 563 email_label: 'Adres email'
524 enabled_label: 'Włączony' 564 enabled_label: 'Włączony'
525 last_login_label: 'Ostatnie logowanie' 565 last_login_label: 'Ostatnie logowanie'
526 twofactor_label: Autoryzacja dwuetapowa 566 # twofactor_email_label: Two factor authentication by email
567 # twofactor_google_label: Two factor authentication by OTP app
527 save: Zapisz 568 save: Zapisz
528 delete: Usuń 569 delete: Usuń
529 delete_confirm: Jesteś pewien? 570 delete_confirm: Jesteś pewien?
@@ -544,7 +585,7 @@ site_credential:
544 create_new_one: Stwórz nowe poświadczenie 585 create_new_one: Stwórz nowe poświadczenie
545 form: 586 form:
546 username_label: 'Nazwa użytkownika' 587 username_label: 'Nazwa użytkownika'
547 host_label: 'Host' 588 host_label: 'Host (subdomain.example.org, .example.org, etc.)'
548 password_label: 'Hasło' 589 password_label: 'Hasło'
549 save: Zapisz 590 save: Zapisz
550 delete: Usuń 591 delete: Usuń
@@ -561,14 +602,18 @@ flashes:
561 password_updated: 'Hasło zaktualizowane' 602 password_updated: 'Hasło zaktualizowane'
562 password_not_updated_demo: "In demonstration mode, you can't change password for this user." 603 password_not_updated_demo: "In demonstration mode, you can't change password for this user."
563 user_updated: 'Informacje zaktualizowane' 604 user_updated: 'Informacje zaktualizowane'
564 rss_updated: 'Informacje RSS zaktualizowane' 605 feed_updated: 'Informacje RSS zaktualizowane'
565 tagging_rules_updated: 'Reguły tagowania zaktualizowane' 606 tagging_rules_updated: 'Reguły tagowania zaktualizowane'
566 tagging_rules_deleted: 'Reguła tagowania usunięta' 607 tagging_rules_deleted: 'Reguła tagowania usunięta'
567 rss_token_updated: 'Token kanału RSS zaktualizowany' 608 feed_token_updated: 'Token kanału RSS zaktualizowany'
609 # feed_token_revoked: 'RSS token revoked'
568 annotations_reset: Zresetuj adnotacje 610 annotations_reset: Zresetuj adnotacje
569 tags_reset: Zresetuj tagi 611 tags_reset: Zresetuj tagi
570 entries_reset: Zresetuj wpisy 612 entries_reset: Zresetuj wpisy
571 archived_reset: Zarchiwizowane wpisy usunięte 613 archived_reset: Zarchiwizowane wpisy usunięte
614 # otp_enabled: Two-factor authentication enabled
615 # tagging_rules_imported: Tagging rules imported
616 # tagging_rules_not_imported: Error while importing tagging rules
572 entry: 617 entry:
573 notice: 618 notice:
574 entry_already_saved: 'Wpis już został dodany %date%' 619 entry_already_saved: 'Wpis już został dodany %date%'
@@ -582,9 +627,11 @@ flashes:
582 entry_starred: 'Wpis oznaczony gwiazdką' 627 entry_starred: 'Wpis oznaczony gwiazdką'
583 entry_unstarred: 'Wpis odznaczony gwiazdką' 628 entry_unstarred: 'Wpis odznaczony gwiazdką'
584 entry_deleted: 'Wpis usunięty' 629 entry_deleted: 'Wpis usunięty'
630 # no_random_entry: 'No article with these criterias was found'
585 tag: 631 tag:
586 notice: 632 notice:
587 tag_added: 'Tag dodany' 633 tag_added: 'Tag dodany'
634 tag_renamed: 'Nazwa taga zmieniona'
588 import: 635 import:
589 notice: 636 notice:
590 failed: 'Nieudany import, prosimy spróbować ponownie.' 637 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 58d2d058..7159569e 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.pt.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.pt.yml
@@ -19,24 +19,26 @@ menu:
19 unread: 'Não lido' 19 unread: 'Não lido'
20 starred: 'Destacado' 20 starred: 'Destacado'
21 archive: 'Arquivo' 21 archive: 'Arquivo'
22 all_articles: 'Todas as entradas' 22 all_articles: 'Todos os artigos'
23 config: 'Configurações' 23 config: 'Configurações'
24 tags: 'Tags' 24 tags: 'Tags'
25 internal_settings: 'Configurações Internas' 25 internal_settings: 'Configurações Internas'
26 import: 'Importar' 26 import: 'Importar'
27 howto: 'How to' 27 howto: 'Ajuda'
28 # developer: 'API clients management' 28 developer: 'Gestão dos clientes API'
29 logout: 'Sair' 29 logout: 'Sair'
30 about: 'Sobre' 30 about: 'Sobre'
31 search: 'Pesquisa' 31 search: 'Pesquisa'
32 save_link: 'Salvar um link' 32 save_link: 'Salvar um link'
33 back_to_unread: 'Voltar para os artigos não lidos' 33 back_to_unread: 'Voltar para os artigos não lidos'
34 users_management: 'Gestão de Usuários' 34 users_management: 'Gestão de Usuários'
35 # site_credentials: 'Site credentials' 35 site_credentials: 'Credenciais do site'
36 quickstart: "Começo Rápido"
36 top: 37 top:
37 add_new_entry: 'Adicionar uma nova entrada' 38 add_new_entry: 'Adicionar um novo artigo'
38 search: 'Pesquisa' 39 search: 'Pesquisa'
39 filter_entries: 'Filtrar entradas' 40 filter_entries: 'Filtrar os artigos'
41 random_entry: Ir para um artigo aleatório desta lista
40 export: 'Exportar' 42 export: 'Exportar'
41 search_form: 43 search_form:
42 input_label: 'Digite aqui sua pesquisa' 44 input_label: 'Digite aqui sua pesquisa'
@@ -50,14 +52,15 @@ footer:
50 stats: 'Desde %user_creation% você leu %nb_archives% artigos. Isso é %per_day% por dia!' 52 stats: 'Desde %user_creation% você leu %nb_archives% artigos. Isso é %per_day% por dia!'
51 53
52config: 54config:
53 page_title: 'Config' 55 page_title: 'Configurações'
54 tab_menu: 56 tab_menu:
55 settings: 'Configurações' 57 settings: 'Configurações'
56 rss: 'RSS' 58 feed: 'RSS'
57 user_info: 'Informação do Usuário' 59 user_info: 'Informação do Usuário'
58 password: 'Senha' 60 password: 'Senha'
59 rules: 'Regras de tags' 61 rules: 'Regras de tags'
60 new_user: 'Adicionar um usuário' 62 new_user: 'Adicionar um usuário'
63 reset: 'Reiniciar minha conta'
61 form: 64 form:
62 save: 'Salvar' 65 save: 'Salvar'
63 form_settings: 66 form_settings:
@@ -65,68 +68,83 @@ config:
65 items_per_page_label: 'Itens por página' 68 items_per_page_label: 'Itens por página'
66 language_label: 'Idioma' 69 language_label: 'Idioma'
67 reading_speed: 70 reading_speed:
68 label: 'Velocidade de leitura' 71 label: 'Velocidade de leitura (palavras por minuto)'
69 help_message: 'Você pode usar ferramentas online para estimar sua velocidade de leitura:' 72 help_message: 'Você pode usar ferramentas online para estimar sua velocidade de leitura:'
70 100_word: 'Posso ler ~100 palavras por minuto'
71 200_word: 'Posso ler ~200 palavras por minuto'
72 300_word: 'Posso ler ~300 palavras por minuto'
73 400_word: 'Posso ler ~400 palavras por minuto'
74 action_mark_as_read: 73 action_mark_as_read:
75 # label: 'Where do you want to be redirected to after marking an article as read?' 74 label: 'Para onde vo deseja ser redirecionado após marcar um artigo como lido?'
76 # redirect_homepage: 'To the homepage' 75 redirect_homepage: 'Para a página inicial'
77 # redirect_current_page: 'To the current page' 76 redirect_current_page: 'Ficar na página atual'
78 pocket_consumer_key_label: 'Chave do consumidor do Pocket para importar conteúdo' 77 pocket_consumer_key_label: 'Chave do consumidor do Pocket para importar conteúdo'
79 # android_configuration: Configure your Android application 78 android_configuration: Configure seu aplicativo Android
80 # android_instruction: "Touch here to prefill your Android application" 79 android_instruction: "Toque aqui para preencher seu aplicativo Android"
81 # help_theme: "wallabag is customizable. You can choose your prefered theme here." 80 help_theme: "wallabag é personalizável. Você pode escolher o seu tema preferido aqui."
82 # help_items_per_page: "You can change the number of articles displayed on each page." 81 help_items_per_page: "Você pode alterar o número de artigos exibidos em cada página."
83 # help_reading_speed: "wallabag calculates a reading time for each article. You can define here, thanks to this list, if you are a fast or a slow reader. wallabag will recalculate the reading time for each article." 82 help_reading_speed: "wallabag calcula um tempo de leitura para cada artigo. Você pode definir aqui, graças a esta lista, se você é um leitor rápido ou lento. O wallabag recalcula o tempo de leitura de cada artigo."
84 # help_language: "You can change the language of wallabag interface." 83 help_language: "Você pode alterar o idioma da interface de wallabag."
85 # help_pocket_consumer_key: "Required for Pocket import. You can create it in your Pocket account." 84 help_pocket_consumer_key: "Necessário para importação desde Pocket. Você pode creá-lo na sua conta de Pocket."
86 form_rss: 85 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.' 86 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' 87 token_label: 'Token RSS'
89 no_token: 'Nenhum Token' 88 no_token: 'Nenhum Token'
90 token_create: 'Criar seu token' 89 token_create: 'Criar seu token'
91 token_reset: 'Gerar novamente seu token' 90 token_reset: 'Gerar novamente seu token'
92 rss_links: 'Links RSS' 91 token_revoke: 'Revocar token'
93 rss_link: 92 feed_links: 'Links RSS'
93 feed_link:
94 unread: 'Não lido' 94 unread: 'Não lido'
95 starred: 'Destacado' 95 starred: 'Destacado'
96 archive: 'Arquivado' 96 archive: 'Arquivado'
97 # all: 'All' 97 all: 'Todos'
98 rss_limit: 'Número de itens no feed' 98 feed_limit: 'Número de itens no feed'
99 form_user: 99 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.' 100 two_factor_description: "Ativar a autenticação de dois fatores significa que você vai receber um e-mail com um código OU que você vai precisar usar um aplicativo OTP (como Google Authenticator, Authy ou FreeOTP) para conseguir um código de utilização única em cada nova conexão não confiável. Você não pode escolher as duas opções."
101 login_label: 'Nome de usuário (não pode ser mudado)'
101 name_label: 'Nome' 102 name_label: 'Nome'
102 email_label: 'E-mail' 103 email_label: 'E-mail'
103 twoFactorAuthentication_label: 'Autenticação de dois passos' 104 two_factor:
104 # help_twoFactorAuthentication: "If you enable 2FA, each time you want to login to wallabag, you'll receive a code by email." 105 emailTwoFactor_label: 'Usando e-mail (receber um código por e-mail)'
106 googleTwoFactor_label: 'Usando um aplicativo OTP (abra o aplicativo, como Google Authenticator, Authy ou FreeOTP, para conseguir um código de usagem único)'
107 table_method: Método
108 table_state: Estado
109 table_action: Ação
110 state_enabled: Ativado
111 state_disabled: Desativado
112 action_email: Usar e-mail
113 action_app: Usar aplicação OTP
105 delete: 114 delete:
106 # title: Delete my account (a.k.a danger zone) 115 title: Apagar minha conta (Zona de perigo)
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: Se você apaga sua conta, TODOS os seus artigos, TODOS os seus tags, TODAS suas anotações e sua conta serão PERMANENTEMENTE removidos (NÃO pode ser DESFEITO). Depois da operação você será desconectado.
108 # confirm: Are you really sure? (THIS CAN'T BE UNDONE) 117 confirm: Tem certeza? (ISSO NÃO PODE SER DESFEITO)
109 # button: Delete my account 118 button: Apagar minha conta
110 reset: 119 reset:
111 # title: Reset area (a.k.a danger zone) 120 title: Reiniciar minha conta (Zona de perigo)
112 # description: By hiting buttons below you'll have ability to remove some informations from your account. Be aware that these actions are IRREVERSIBLE. 121 description: Apertando os botões aqui em baixo você poderá remover informações da sua conta. Saiba que eessas ações sao IRREVERSIVEIS.
113 # annotations: Remove ALL annotations 122 annotations: Remover TODAS as anotações
114 # tags: Remove ALL tags 123 tags: Remover TODOS os tags
115 # entries: Remove ALL entries 124 entries: Remover TODOS os artigos
116 # archived: Remove ALL archived entries 125 archived: Remover TODOS os artigos arquivados
117 # confirm: Are you really really sure? (THIS CAN'T BE UNDONE) 126 confirm: Tem certeza? (ISSO NÃO PODE SER DESFEITO)
118 form_password: 127 form_password:
119 # description: "You can change your password here. Your new password should by at least 8 characters long." 128 description: "Você pode mudar a sua senha aqui. A nova senha deve ter pelo menos 8 caracteres."
120 old_password_label: 'Senha atual' 129 old_password_label: 'Senha atual'
121 new_password_label: 'Nova senha' 130 new_password_label: 'Nova senha'
122 repeat_new_password_label: 'Repita a nova senha' 131 repeat_new_password_label: 'Repita a nova senha'
123 form_rules: 132 form_rules:
124 if_label: 'if' 133 if_label: 'se'
125 then_tag_as_label: 'então coloque a tag' 134 then_tag_as_label: 'então coloque a tag'
126 delete_rule_label: 'apagar' 135 delete_rule_label: 'apagar'
127 edit_rule_label: 'editar' 136 edit_rule_label: 'editar'
128 rule_label: 'Regras' 137 rule_label: 'Regras'
129 tags_label: 'Tags' 138 tags_label: 'Tags'
139 card:
140 new_tagging_rule: Criar uma regra de tag
141 import_tagging_rules: Importar regras de tags
142 import_tagging_rules_detail: Você precisa selecionar o arquivo JSON exportado previamente.
143 export_tagging_rules: Exportar regras de tags
144 export_tagging_rules_detail: Isso vai baixar um arquivo JSON que você poderá usar para importar regras de marcação em outro local ou fazer uma cópia de segurança delas.
145 file_label: Arquivo JSON
146 import_submit: Importar
147 export: Exportar
130 faq: 148 faq:
131 title: 'FAQ' 149 title: 'FAQ'
132 tagging_rules_definition_title: 'O que as « regras de tags » significam?' 150 tagging_rules_definition_title: 'O que as « regras de tags » significam?'
@@ -135,18 +153,18 @@ config:
135 how_to_use_them_description: 'Vamos dizer que você deseja adicionar a tag « <i>leitura rápida</i> » quando o tempo de leitura for menor que 3 minutos.<br />Neste caso, você deve « readingTime &lt;= 3 » no campo <i>Regra</i> e « <i>leitura rápida</i> » no campo <i>Tags</i>.<br />Diversas tags podem ser adicionadas simultâneamente separando-as com vírgula: « <i>leitura rápida, precisa ser lido</i> »<br />Regras complexas podem ser escritas usando os seguintes operadores pré-definidos: if « <i>readingTime &gt;= 5 AND domainName = "github.com"</i> » então adicione a tag « <i>leitura longa, github </i> »' 153 how_to_use_them_description: 'Vamos dizer que você deseja adicionar a tag « <i>leitura rápida</i> » quando o tempo de leitura for menor que 3 minutos.<br />Neste caso, você deve « readingTime &lt;= 3 » no campo <i>Regra</i> e « <i>leitura rápida</i> » no campo <i>Tags</i>.<br />Diversas tags podem ser adicionadas simultâneamente separando-as com vírgula: « <i>leitura rápida, precisa ser lido</i> »<br />Regras complexas podem ser escritas usando os seguintes operadores pré-definidos: if « <i>readingTime &gt;= 5 AND domainName = "github.com"</i> » então adicione a tag « <i>leitura longa, github </i> »'
136 variables_available_title: 'Quais variáveis e operadores eu posso usar para escrever regras?' 154 variables_available_title: 'Quais variáveis e operadores eu posso usar para escrever regras?'
137 variables_available_description: 'As seguintes variáveis e operadores podem ser usados para criar regras de tags:' 155 variables_available_description: 'As seguintes variáveis e operadores podem ser usados para criar regras de tags:'
138 meaning: 'Meaning' 156 meaning: 'Significado'
139 variable_description: 157 variable_description:
140 label: 'Variável' 158 label: 'Variável'
141 title: 'Título da entrada' 159 title: 'Título do artigo'
142 url: 'URL da entrada' 160 url: 'URL do artigo'
143 isArchived: 'Se a entrada está arquivada ou não' 161 isArchived: 'Se o artigo está arquivado ou não'
144 isDestacado: 'Se a entrada está destacada ou não' 162 isDestacado: 'Se o artigo está destacado ou não'
145 content: 'O conteúdo da entrada' 163 content: 'O conteúdo do artigo'
146 language: 'O idioma da entrada' 164 language: 'O idioma do artigo'
147 mimetype: 'O mime-type da entrada' 165 mimetype: 'O mime-type do artigo'
148 readingTime: 'O tempo estimado de leitura da entrada, em minutos' 166 readingTime: 'O tempo estimado de leitura do artigo, em minutos'
149 domainName: 'O domínio da entrada' 167 domainName: 'O domínio do artigo'
150 operator_description: 168 operator_description:
151 label: 'Operador' 169 label: 'Operador'
152 less_than: 'Menor que...' 170 less_than: 'Menor que...'
@@ -157,22 +175,31 @@ config:
157 not_equal_to: 'Diferente de...' 175 not_equal_to: 'Diferente de...'
158 or: 'Uma regra OU outra' 176 or: 'Uma regra OU outra'
159 and: 'Uma regra E outra' 177 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>' 178 matches: 'Testa que um <i>assunto</i> corresponde a uma <i>pesquisa</i> (maiúscula ou minúscula).<br />Exemplo: <code>title matches "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>' 179 notmatches: 'Testa que um <i>assunto</i> não corresponde a uma <i>search</i> (maiúscula ou minúscula).<br />Exemplo: <code>title notmatches "futebol"</code>'
180 otp:
181 page_title: Autenticação de dois fatores
182 app:
183 two_factor_code_description_1: Você acaba de ativar a autenticação de dois fatores com aplicativo OTP, abra seu aplicativo OTP e consegua um código de usagem único. Vai desaparecer ao recargar a página.
184 two_factor_code_description_2: 'Você pode escanear este código QR com seu aplicativo:'
185 two_factor_code_description_3: 'Não esqueça de guardar os códigos de segurança em um lugar seguro, você poderá usá-los se você perder o acesso ao seu aplicativo OTP:'
186 two_factor_code_description_4: 'Teste um código gerado pelo seu aplicativo OTP:'
187 cancel: Cancelar
188 enable: Ativar
162 189
163entry: 190entry:
164 default_title: 'Título da entrada' 191 default_title: 'Título do artigo'
165 page_titles: 192 page_titles:
166 unread: 'Entradas não lidas' 193 unread: 'Artigos não lidos'
167 starred: 'Entradas destacadas' 194 starred: 'Artigos destacados'
168 archived: 'Entradas arquivadas' 195 archived: 'Artigos arquivados'
169 filtered: 'Entradas filtradas' 196 filtered: 'Artigos filtrados'
170 filtered_tags: 'Filtrar por tags:' 197 filtered_tags: 'Filtrar por tags:'
171 # filtered_search: 'Filtered by search:' 198 filtered_search: 'Filtrar por busca:'
172 untagged: 'Entradas sem tags' 199 untagged: 'Entradas sem tags'
173 # all: 'All entries' 200 all: 'Todos os artigos'
174 list: 201 list:
175 number_on_the_page: '{0} Não existem entradas.|{1} Existe uma entrada.|]1,Inf[ Existem %count% entradas.' 202 number_on_the_page: '{0} Não existem artigos.|{1} Existe um artigo.|]1,Inf[ Existem %count% artigos.'
176 reading_time: 'tempo estimado de leitura' 203 reading_time: 'tempo estimado de leitura'
177 reading_time_minutes: 'tempo estimado de leitura: %readingTime% min' 204 reading_time_minutes: 'tempo estimado de leitura: %readingTime% min'
178 reading_time_less_one_minute: 'tempo estimado de leitura: &lt; 1 min' 205 reading_time_less_one_minute: 'tempo estimado de leitura: &lt; 1 min'
@@ -190,12 +217,12 @@ entry:
190 archived_label: 'Arquivado' 217 archived_label: 'Arquivado'
191 starred_label: 'Destacado' 218 starred_label: 'Destacado'
192 unread_label: 'Não Lido' 219 unread_label: 'Não Lido'
193 preview_picture_label: 'Possui uma imagem de preview' 220 preview_picture_label: 'Possui uma imagem de pré-visualização'
194 preview_picture_help: 'Imagem de preview' 221 preview_picture_help: 'Imagem de pré-visualização'
195 # is_public_label: 'Has a public link' 222 is_public_label: 'Tem um link público'
196 # is_public_help: 'Public link' 223 is_public_help: 'Link público'
197 language_label: 'Idioma' 224 language_label: 'Idioma'
198 # http_status_label: 'HTTP status' 225 http_status_label: 'Código de estado HTTP'
199 reading_time: 226 reading_time:
200 label: 'Tempo de leitura em minutos' 227 label: 'Tempo de leitura em minutos'
201 from: 'de' 228 from: 'de'
@@ -214,11 +241,11 @@ entry:
214 back_to_homepage: 'Voltar' 241 back_to_homepage: 'Voltar'
215 set_as_read: 'Marcar como lido' 242 set_as_read: 'Marcar como lido'
216 set_as_unread: 'Marcar como não lido' 243 set_as_unread: 'Marcar como não lido'
217 set_as_starred: 'Alternar destaque' 244 set_as_starred: 'Marcar como destacado'
218 view_original_article: 'Artigo original' 245 view_original_article: 'Artigo original'
219 re_fetch_content: 'Recapturar o conteúdo' 246 re_fetch_content: 'Recapturar o conteúdo'
220 delete: 'Apagar' 247 delete: 'Apagar'
221 add_a_tag: 'Adicionar uma tag' 248 add_a_tag: 'Adicionar um tag'
222 share_content: 'Compartilhar' 249 share_content: 'Compartilhar'
223 share_email_label: 'E-mail' 250 share_email_label: 'E-mail'
224 public_link: 'link público' 251 public_link: 'link público'
@@ -232,27 +259,32 @@ entry:
232 original_article: 'original' 259 original_article: 'original'
233 annotations_on_the_entry: '{0} Sem anotações|{1} Uma anotação|]1,Inf[ %nbAnnotations% anotações' 260 annotations_on_the_entry: '{0} Sem anotações|{1} Uma anotação|]1,Inf[ %nbAnnotations% anotações'
234 created_at: 'Data de criação' 261 created_at: 'Data de criação'
235 # published_at: 'Publication date' 262 published_at: 'Data de publicação'
236 # published_by: 'Published by' 263 published_by: 'Publicado por'
237 # provided_by: 'Provided by' 264 provided_by: 'Fornecido por'
238 new: 265 new:
239 page_title: 'Salvar nova entrada' 266 page_title: 'Salvar novo artigo'
240 placeholder: 'http://website.com' 267 placeholder: 'https://website.pt'
241 form_new: 268 form_new:
242 url_label: Url 269 url_label: Url
243 search: 270 search:
244 # placeholder: 'What are you looking for?' 271 placeholder: 'O que vo está procurando?'
245 edit: 272 edit:
246 page_title: 'Editar uma entrada' 273 page_title: 'Editar um artigo'
247 title_label: 'Título' 274 title_label: 'Título'
248 url_label: 'Url' 275 url_label: 'Url'
249 # origin_url_label: 'Origin url (from where you found that entry)' 276 origin_url_label: 'URL de origem url (onde vo encontrou este artigo)'
250 save_label: 'Salvar' 277 save_label: 'Salvar'
251 public: 278 public:
252 shared_by_wallabag: "Este artigo foi compartilhado pelo <a href='%wallabag_instance%'>wallabag</a>" 279 shared_by_wallabag: "Este artigo foi compartilhado pelo <a href='%wallabag_instance%'>wallabag</a>"
253 confirm: 280 confirm:
254 # delete: "Are you sure you want to remove that article?" 281 delete: Tem certeza de que deseja remover este artigo?"
255 # delete_tag: "Are you sure you want to remove that tag from that article?" 282 delete_tag: "Tem certeza de que deseja remover este tag deste artigo?"
283 metadata:
284 reading_time: "Tempo estimado de leitura"
285 reading_time_minutes_short: "%readingTime% min"
286 address: "Endereço"
287 added_on: "Adicionado o"
256 288
257about: 289about:
258 page_title: 'Sobre' 290 page_title: 'Sobre'
@@ -274,7 +306,7 @@ about:
274 bug_reports: 'Informar bugs' 306 bug_reports: 'Informar bugs'
275 support: '<a href="https://github.com/wallabag/wallabag/issues">no GitHub</a>' 307 support: '<a href="https://github.com/wallabag/wallabag/issues">no GitHub</a>'
276 helping: 308 helping:
277 description: 'wallabag é livre e software livre. Você pode nos ajudar:' 309 description: 'wallabag é um software livre e gratuito. Você pode nos ajudar:'
278 by_contributing: 'contribuindo com o projeto:' 310 by_contributing: 'contribuindo com o projeto:'
279 by_contributing_2: 'uma lista de todas as nossas necessidades' 311 by_contributing_2: 'uma lista de todas as nossas necessidades'
280 by_paypal: 'via Paypal' 312 by_paypal: 'via Paypal'
@@ -289,11 +321,11 @@ howto:
289 page_title: 'How to' 321 page_title: 'How to'
290 page_description: 'Existem diferentes formas de salvar um artigo:' 322 page_description: 'Existem diferentes formas de salvar um artigo:'
291 tab_menu: 323 tab_menu:
292 # add_link: "Add a link" 324 add_link: "Adicionar um link"
293 # shortcuts: "Use shortcuts" 325 shortcuts: "Usar atalhos"
294 top_menu: 326 top_menu:
295 browser_addons: 'Extensões de navegadores' 327 browser_addons: 'Extensões de navegadores'
296 mobile_apps: "App's móveis" 328 mobile_apps: "Aplicativos móveis"
297 bookmarklet: 'Bookmarklet' 329 bookmarklet: 'Bookmarklet'
298 form: 330 form:
299 description: 'Obrigado por este formulário' 331 description: 'Obrigado por este formulário'
@@ -308,34 +340,34 @@ howto:
308 ios: 'na iTunes Store' 340 ios: 'na iTunes Store'
309 windows: 'na Microsoft Store' 341 windows: 'na Microsoft Store'
310 bookmarklet: 342 bookmarklet:
311 description: 'Arraste e solve este link na sua barra de favoritos:' 343 description: 'Arraste e solte este link na sua barra de favoritos:'
312 shortcuts: 344 shortcuts:
313 # page_description: Here are the shortcuts available in wallabag. 345 page_description: Aqui estão os atalhos disponíveis no wallabag.
314 # shortcut: Shortcut 346 shortcut: Atalho
315 # action: Action 347 action: Ação
316 # all_pages_title: Shortcuts available in all pages 348 all_pages_title: Atalhos disponíveis em todas as páginas
317 # go_unread: Go to unread 349 go_unread: Ir para não lidos
318 # go_starred: Go to starred 350 go_starred: Ir para favoritos
319 # go_archive: Go to archive 351 go_archive: Ir para arquivados
320 # go_all: Go to all entries 352 go_all: Ir a todos os artigos
321 # go_tags: Go to tags 353 go_tags: Ir para tags
322 # go_config: Go to config 354 go_config: Ir para configuração
323 # go_import: Go to import 355 go_import: Ir para importar
324 # go_developers: Go to developers 356 go_developers: Ir para desenvolvedores
325 # go_howto: Go to howto (this page!) 357 go_howto: Ir para ajuda (esta página)
326 # go_logout: Logout 358 go_logout: Ir para sair
327 # list_title: Shortcuts available in listing pages 359 list_title: Atalhos disponíveis em páginas de listagem
328 # search: Display the search form 360 search: Exibir o formulário de pesquisa
329 # article_title: Shortcuts available in entry view 361 article_title: Atalhos disponíveis no artigo
330 # open_original: Open original URL of the entry 362 open_original: Abrir URL original do artigo
331 # toggle_favorite: Toggle star status for the entry 363 toggle_favorite: Marcar o artigo como destacado / não destacado
332 # toggle_archive: Toggle read status for the entry 364 toggle_archive: Marcar artigo como lido / não lido
333 # delete: Delete the entry 365 delete: Apagar o artigo
334 # material_title: Shortcuts available with Material theme only 366 material_title: Atalhos disponíveis apenas com o tema Material
335 # add_link: Add a new link 367 add_link: Adicionar um novo artigo
336 # hide_form: Hide the current form (search or new link) 368 hide_form: Ocultar o formulário atual (pesquisa ou novo artigo)
337 # arrows_navigation: Navigate through articles 369 arrows_navigation: Navegar pelo artigos
338 # open_article: Display the selected entry 370 open_article: Exibir o artigo selecionado
339 371
340quickstart: 372quickstart:
341 page_title: 'Começo Rápido' 373 page_title: 'Começo Rápido'
@@ -348,7 +380,7 @@ quickstart:
348 title: 'Configurar a aplicação' 380 title: 'Configurar a aplicação'
349 description: 'Para ter uma aplicação que atende você, dê uma olhada na configuração do wallabag.' 381 description: 'Para ter uma aplicação que atende você, dê uma olhada na configuração do wallabag.'
350 language: 'Alterar idioma e design' 382 language: 'Alterar idioma e design'
351 rss: 'Habilitar feeds RSS' 383 feed: 'Habilitar feeds RSS'
352 tagging_rules: 'Escrever regras para acrescentar tags automaticamente em seus artigos' 384 tagging_rules: 'Escrever regras para acrescentar tags automaticamente em seus artigos'
353 admin: 385 admin:
354 title: 'Administração' 386 title: 'Administração'
@@ -369,8 +401,8 @@ quickstart:
369 pocket: 'Migrar do Pocket' 401 pocket: 'Migrar do Pocket'
370 wallabag_v1: 'Migrar do wallabag v1' 402 wallabag_v1: 'Migrar do wallabag v1'
371 wallabag_v2: 'Migrar do wallabag v2' 403 wallabag_v2: 'Migrar do wallabag v2'
372 readability: 'Migrate from Readability' 404 readability: 'Migrar do Readability'
373 instapaper: 'Migrate from Instapaper' 405 instapaper: 'Migrar do Instapaper'
374 developer: 406 developer:
375 title: 'Desenvolvedores' 407 title: 'Desenvolvedores'
376 description: 'Nós também agradecemos os desenvolvedores: Docker, API, traduções, etc.' 408 description: 'Nós também agradecemos os desenvolvedores: Docker, API, traduções, etc.'
@@ -381,7 +413,7 @@ quickstart:
381 description: "Existem muitas funcionalidades no wallabag. Não hesite em ler o manual para conhecê-las e aprender como usá-las." 413 description: "Existem muitas funcionalidades no wallabag. Não hesite em ler o manual para conhecê-las e aprender como usá-las."
382 annotate: 'Anotar seu artigo' 414 annotate: 'Anotar seu artigo'
383 export: 'Converter seu artigo em ePUB ou PDF' 415 export: 'Converter seu artigo em ePUB ou PDF'
384 search_filters: 'veja coo você pode encontrar um artigo usanndo o motor de busca e filtros' 416 search_filters: 'veja como você pode encontrar um artigo usando o motor de busca e filtros'
385 fetching_errors: 'O que eu posso fazer quando um artigo encontra erros na recuperação?' 417 fetching_errors: 'O que eu posso fazer quando um artigo encontra erros na recuperação?'
386 all_docs: 'E outros muitos artigos!' 418 all_docs: 'E outros muitos artigos!'
387 support: 419 support:
@@ -396,21 +428,25 @@ tag:
396 list: 428 list:
397 number_on_the_page: '{0} Não existem tags.|{1} Uma tag.|]1,Inf[ Existem %count% tags.' 429 number_on_the_page: '{0} Não existem tags.|{1} Uma tag.|]1,Inf[ Existem %count% tags.'
398 see_untagged_entries: 'Ver entradas sem tags' 430 see_untagged_entries: 'Ver entradas sem tags'
431 no_untagged_entries: 'Não há entradas sem tags.'
399 new: 432 new:
400 # add: 'Add' 433 add: 'Adicionar'
401 # placeholder: 'You can add several tags, separated by a comma.' 434 placeholder: 'Você pode adicionar varios tags, separados por vírgulas.'
435 rename:
436 placeholder: 'Você pode atualizar o nome do tag.'
402 437
403# export: 438export:
404# 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>' 439 footer_template: '<div style="text-align:center;"><p>Producido por wallabag com %method%</p><p>Por favor abra <a href="https://github.com/wallabag/wallabag/issues">um bolheto</a> se você tiver problemas com a exibição deste E-Book no seu dispositivo.</p></div>'
440 unknown: 'Desconhecido'
405 441
406import: 442import:
407 page_title: 'Importar' 443 page_title: 'Importar'
408 page_description: 'Bem-vindo ao importador do wallabag. Por favo selecione o serviço do qual deseja migrar.' 444 page_description: 'Bem-vindo ao importador do wallabag. Por favor selecione o serviço do qual deseja migrar.'
409 action: 445 action:
410 import_contents: 'Importar conteúdos' 446 import_contents: 'Importar conteúdos'
411 form: 447 form:
412 mark_as_read_title: 'Marcar todos como lidos?' 448 mark_as_read_title: 'Marcar todos como lidos?'
413 mark_as_read_label: 'Marcar todas as entradas importadas como lidas' 449 mark_as_read_label: 'Marcar todos os artigos importados como lidos'
414 file_label: 'Arquivo' 450 file_label: 'Arquivo'
415 save_label: 'Carregar arquivo' 451 save_label: 'Carregar arquivo'
416 pocket: 452 pocket:
@@ -429,13 +465,16 @@ import:
429 wallabag_v2: 465 wallabag_v2:
430 page_title: 'Importar > Wallabag v2' 466 page_title: 'Importar > Wallabag v2'
431 description: 'Com este importador você importa todos os seus artigos do wallabag v2. Vá em Todos os artigos e então, na barra lateral de exportação, clique em "JSON". Você irá criar um arquivo "All articles.json".' 467 description: 'Com este importador você importa todos os seus artigos do wallabag v2. Vá em Todos os artigos e então, na barra lateral de exportação, clique em "JSON". Você irá criar um arquivo "All articles.json".'
468 # elcurator:
469 # page_title: 'Import > elCurator'
470 # description: 'This importer will import all your elCurator articles. Go to your preferences in your elCurator account and then, export your content. You will have a JSON file.'
432 readability: 471 readability:
433 page_title: 'Importar > Readability' 472 page_title: 'Importar > Readability'
434 description: 'Este importador pode importar todos os artigos do Readability. Nas página ferramentas (https://www.readability.com/tools/), clique em "Export your data" na seção "Data Export". Você receberá um e-mail para fazer o download de um json (que de fato não termina com .json).' 473 description: 'Este importador pode importar todos os artigos do Readability. Nas página ferramentas (https://www.readability.com/tools/), clique em "Export your data" na seção "Data Export". Você receberá um e-mail para fazer o download de um json (que de fato não termina com .json).'
435 how_to: 'Por favor, selecione sua exportação do Readability e clique no botão abaixo para importá-la.' 474 how_to: 'Por favor, selecione sua exportação do Readability e clique no botão abaixo para importá-la.'
436 worker: 475 worker:
437 enabled: "A importação é feita assíncronamente. Uma vez que a tarefa de importação é iniciada, um trabalho externo pode executar tarefas uma por vez. O serviço atual é:" 476 enabled: "A importação é feita assíncronamente. Uma vez que a tarefa de importação é iniciada, um trabalho externo pode executar tarefas uma por vez. O serviço atual é:"
438 # download_images_warning: "You enabled downloading images for your articles. Combined with classic import it can take ages to proceed (or maybe failed). We <strong>strongly recommend</strong> to enable asynchronous import to avoid errors." 477 download_images_warning: "Você ativou o donwload de imagens para os seus artigos. Combinado com a importação clásica isso pode demorar muito tempo (ou mesmo falhar). Nós <strong>recomendamos fortemente</strong> que você ative a importação assíncrona para evitar erros."
439 firefox: 478 firefox:
440 page_title: 'Importar > Firefox' 479 page_title: 'Importar > Firefox'
441 description: "Com este importador você importa todos os favoritos de seu Firefox. Somente vá até seus favoritos (Ctrl+Maj+O), e em \"Importar e Backup\" e escolha \"Backup...\". Você terá então um arquivo .json." 480 description: "Com este importador você importa todos os favoritos de seu Firefox. Somente vá até seus favoritos (Ctrl+Maj+O), e em \"Importar e Backup\" e escolha \"Backup...\". Você terá então um arquivo .json."
@@ -449,12 +488,12 @@ import:
449 description: 'Este importador pode importar todos os artigos do seu Instapaper. Nas página de configurações (https://www.instapaper.com/user), clique em "Download .CSV file" na seção "Export". Um arquivo CSV será baixado (algo como "instapaper-export.csv").' 488 description: 'Este importador pode importar todos os artigos do seu Instapaper. Nas página de configurações (https://www.instapaper.com/user), clique em "Download .CSV file" na seção "Export". Um arquivo CSV será baixado (algo como "instapaper-export.csv").'
450 how_to: 'Por favor, selecione sua exportação do seu Instapaper e clique no botão abaixo para importá-la.' 489 how_to: 'Por favor, selecione sua exportação do seu Instapaper e clique no botão abaixo para importá-la.'
451 pinboard: 490 pinboard:
452 # page_title: "Import > Pinboard" 491 page_title: "Importar > Pinboard"
453 # description: 'This importer will import all your Instapaper articles. On the backup (https://pinboard.in/settings/backup) page, click on "JSON" in the "Bookmarks" section. A JSON file will be downloaded (like "pinboard_export").' 492 description: 'Este importador pode importar todos os artigos do seu Pinboard. Na página de cópia de segurança (https://pinboard.in/settings/backup), clique em "JSON" na seção "Bookmarks". Um arquivo JSON será baixado (algo como "pinboard_export").'
454 # how_to: 'Please select your Pinboard export and click on the below button to upload and import it.' 493 how_to: 'Por favor selecione a sua exportação Pinboard e clique no botão em baixo para carregá-lo e importá-lo.'
455 494
456developer: 495developer:
457 # page_title: 'API clients management' 496 page_title: 'Gestão dos clientes API'
458 welcome_message: 'Bem-vindo a API do wallabag' 497 welcome_message: 'Bem-vindo a API do wallabag'
459 documentation: 'Documentação' 498 documentation: 'Documentação'
460 how_to_first_app: 'Como criar minha primeira aplicação' 499 how_to_first_app: 'Como criar minha primeira aplicação'
@@ -475,23 +514,24 @@ developer:
475 warn_message_2: 'Se você remover isso, todo o aplicativo configurado com este cliente não poderá se autenticar no seu wallabag.' 514 warn_message_2: 'Se você remover isso, todo o aplicativo configurado com este cliente não poderá se autenticar no seu wallabag.'
476 action: 'Remover este cliente' 515 action: 'Remover este cliente'
477 client: 516 client:
478 # page_title: 'API clients management > Novo cliente' 517 page_title: 'Gestão de clientes API > Novo cliente'
479 page_description: 'Você está prestes a criar um novo cliente. Por favor preencha o campo abaixo para a URI de redirecionamento de sua aplicação.' 518 page_description: 'Você está prestes a criar um novo cliente. Por favor preencha o campo abaixo para a URI de redirecionamento de sua aplicação.'
480 form: 519 form:
481 name_label: 'Nome do cliente' 520 name_label: 'Nome do cliente'
482 redirect_uris_label: 'URIs de redirecionamento' 521 redirect_uris_label: 'URIs de redirecionamento'
483 save_label: 'Criar um novo cliente' 522 save_label: 'Criar um novo cliente'
484 action_back: 'Voltar' 523 action_back: 'Voltar'
524 copy_to_clipboard: Copiar
485 client_parameter: 525 client_parameter:
486 # page_title: 'API clients management > Parâmetros de clientes' 526 page_title: 'Gestão de clientes API > Parâmetros de clientes'
487 page_description: 'Aqui estão os parâmetros de seus clientes.' 527 page_description: 'Aqui estão os parâmetros de seus clientes.'
488 field_name: 'Nome do cliente' 528 field_name: 'Nome do cliente'
489 field_id: 'ID do cliente' 529 field_id: 'ID do cliente'
490 field_secret: 'Chave do cliente' 530 field_secret: 'Chave do cliente'
491 back: 'Voltar' 531 back: 'Voltar'
492 read_howto: 'Leia o how-to "Criar minha primeira aplicação"' 532 read_howto: 'Leia o guia "Criar minha primeira aplicação"'
493 howto: 533 howto:
494 # page_title: 'API clients management > Criar minha primeira aplicação' 534 page_title: 'Gestão de clientes API > Criar minha primeira aplicação'
495 description: 535 description:
496 paragraph_1: 'Os seguintes comandos fazem uso da <a href="https://github.com/jkbrzt/httpie">biblioteca HTTPie</a>. Tenha certeza que ela está instalada em seu servidor antes de usá-la.' 536 paragraph_1: 'Os seguintes comandos fazem uso da <a href="https://github.com/jkbrzt/httpie">biblioteca HTTPie</a>. Tenha certeza que ela está instalada em seu servidor antes de usá-la.'
497 paragraph_2: 'Você precisa de um token para a comunicação entre sua aplicação terceira e a API do wallabag.' 537 paragraph_2: 'Você precisa de um token para a comunicação entre sua aplicação terceira e a API do wallabag.'
@@ -523,36 +563,37 @@ user:
523 email_label: 'E-mail' 563 email_label: 'E-mail'
524 enabled_label: 'Habilitado' 564 enabled_label: 'Habilitado'
525 last_login_label: 'Último login' 565 last_login_label: 'Último login'
526 twofactor_label: 'Autenticação de dois passos' 566 twofactor_email_label: Autenticação de dois fatores por e-mail
567 twofactor_google_label: Autenticação de dois fatores por aplicativo OTP
527 save: 'Salvar' 568 save: 'Salvar'
528 delete: 'Apagar' 569 delete: 'Apagar'
529 delete_confirm: 'Tem certeza?' 570 delete_confirm: 'Tem certeza?'
530 back_to_list: 'Voltar para a lista' 571 back_to_list: 'Voltar para a lista'
531 search: 572 search:
532 # placeholder: Filter by username or email 573 placeholder: Filtrar por nome de usuário ou e-mail
533 574
534site_credential: 575site_credential:
535 # page_title: Site credentials management 576 page_title: Gerenciamento de credenciais do site
536 # new_site_credential: Create a credential 577 new_site_credential: Criar uma credencial
537 # edit_site_credential: Edit an existing credential 578 edit_site_credential: Editar uma credencial existente
538 # description: "Here you can manage all credentials for sites which required them (create, edit and delete), like a paywall, an authentication, etc." 579 description: "Aqui você pode gerenciar todas as credenciais para os sites que precisam delas (criar, editar e apagar), como um paywall, uma autenticação, etc."
539 list: 580 list:
540 actions: 'Ações' 581 actions: 'Ações'
541 edit_action: 'Editar' 582 edit_action: 'Editar'
542 yes: 'Sim' 583 yes: 'Sim'
543 no: 'Não' 584 no: 'Não'
544 # create_new_one: Create a new credential 585 create_new_one: Criar uma nova credencial
545 form: 586 form:
546 # username_label: 'Username' 587 username_label: 'Nome de usuário'
547 # host_label: 'Host' 588 host_label: 'Host (subdominio.exemplo.org, .exemplo.org, etc.)'
548 # password_label: 'Password' 589 password_label: 'Senha'
549 save: 'Salvar' 590 save: 'Salvar'
550 delete: 'Apagar' 591 delete: 'Apagar'
551 delete_confirm: 'Tem certeza?' 592 delete_confirm: 'Tem certeza?'
552 back_to_list: 'Voltar para a lista' 593 back_to_list: 'Voltar para a lista'
553 594
554error: 595error:
555 # page_title: An error occurred 596 page_title: Um erro ocorreu
556 597
557flashes: 598flashes:
558 config: 599 config:
@@ -560,31 +601,37 @@ flashes:
560 config_saved: 'Configiração salva.' 601 config_saved: 'Configiração salva.'
561 password_updated: 'Senha atualizada' 602 password_updated: 'Senha atualizada'
562 password_not_updated_demo: 'Em modo de demonstração, você não pode alterar a senha deste usuário.' 603 password_not_updated_demo: 'Em modo de demonstração, você não pode alterar a senha deste usuário.'
563 # user_updated: 'Information updated' 604 user_updated: 'Informação atualizada'
564 rss_updated: 'Informação de RSS atualizada' 605 feed_updated: 'Informação de RSS atualizada'
565 tagging_rules_updated: 'Regras de tags atualizadas' 606 tagging_rules_updated: 'Regras de tags atualizadas'
566 tagging_rules_deleted: 'Regra de tag apagada' 607 tagging_rules_deleted: 'Regra de tag apagada'
567 rss_token_updated: 'Token RSS atualizado' 608 feed_token_updated: 'Token RSS atualizado'
568 # annotations_reset: Annotations reset 609 feed_token_revoked: 'Token RSS revogado'
569 # tags_reset: Tags reset 610 annotations_reset: Anotações reinicializadas
570 # entries_reset: Entries reset 611 tags_reset: Tags reinicializados
571 # archived_reset: Archived entries deleted 612 entries_reset: Artigos reinicializados
613 archived_reset: Artigos arquivados apagados
614 otp_enabled: Autenticação de dois fatores ativada
615 tagging_rules_imported: Regras de tags importadas
616 tagging_rules_not_imported: Erro ao importar regras de tags
572 entry: 617 entry:
573 notice: 618 notice:
574 entry_already_saved: 'Entrada já foi salva em %date%' 619 entry_already_saved: 'Artigo já foi salvo em %date%'
575 entry_saved: 'Entrada salva' 620 entry_saved: 'Artigo salvo'
576 entry_saved_failed: 'Failed to save entry' 621 entry_saved_failed: 'Artigo salvo mas falha na recuperação do conteúdo'
577 entry_updated: 'Entrada atualizada' 622 entry_updated: 'Artigo atualizado'
578 entry_reloaded: 'Entrada recarregada' 623 entry_reloaded: 'Artigo recarregado'
579 entry_reloaded_failed: 'Falha em recarregar a entrada' 624 entry_reloaded_failed: 'Artigo em recarregar o artigo'
580 entry_archived: 'Entrada arquivada' 625 entry_archived: 'Artigo arquivado'
581 entry_unarchived: 'Entrada desarquivada' 626 entry_unarchived: 'Artigo desarquivado'
582 entry_starred: 'Entrada destacada' 627 entry_starred: 'Artigo destacado'
583 entry_unstarred: 'Entrada não destacada' 628 entry_unstarred: 'Artigo não destacado'
584 entry_deleted: 'Entrada apagada' 629 entry_deleted: 'Artigo apagado'
630 no_random_entry: 'Nehum artigo com esses criterios foi encontrado'
585 tag: 631 tag:
586 notice: 632 notice:
587 tag_added: 'Tag adicionada' 633 tag_added: 'Tag adicionada'
634 tag_renamed: 'Tag renomeado'
588 import: 635 import:
589 notice: 636 notice:
590 failed: 'Importação falhou, por favor tente novamente.' 637 failed: 'Importação falhou, por favor tente novamente.'
@@ -605,6 +652,6 @@ flashes:
605 deleted: 'Usuário "%username%" removido' 652 deleted: 'Usuário "%username%" removido'
606 site_credential: 653 site_credential:
607 notice: 654 notice:
608 # added: 'Site credential for "%host%" added' 655 added: 'Credencial do site para "%host%" foi adicionada'
609 # updated: 'Site credential for "%host%" updated' 656 updated: 'Credencial do site pa "%host%" atualizada'
610 # deleted: 'Site credential for "%host%" deleted' 657 deleted: 'Credencial do site pa "%host%" removida'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml
index 564fed94..f671da8d 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml
@@ -33,10 +33,12 @@ menu:
33 back_to_unread: 'Înapoi la articolele necitite' 33 back_to_unread: 'Înapoi la articolele necitite'
34 # users_management: 'Users management' 34 # users_management: 'Users management'
35 # site_credentials: 'Site credentials' 35 # site_credentials: 'Site credentials'
36 # quickstart: "Quickstart"
36 top: 37 top:
37 add_new_entry: 'Introdu un nou articol' 38 add_new_entry: 'Introdu un nou articol'
38 search: 'Căutare' 39 search: 'Căutare'
39 filter_entries: 'Filtrează articolele' 40 filter_entries: 'Filtrează articolele'
41 # random_entry: Jump to a random entry from that list
40 # export: 'Export' 42 # export: 'Export'
41 search_form: 43 search_form:
42 input_label: 'Introdu căutarea ta' 44 input_label: 'Introdu căutarea ta'
@@ -53,11 +55,12 @@ config:
53 page_title: 'Configurație' 55 page_title: 'Configurație'
54 tab_menu: 56 tab_menu:
55 settings: 'Setări' 57 settings: 'Setări'
56 rss: 'RSS' 58 feed: 'RSS'
57 user_info: 'Informații despre utilizator' 59 user_info: 'Informații despre utilizator'
58 password: 'Parolă' 60 password: 'Parolă'
59 # rules: 'Tagging rules' 61 # rules: 'Tagging rules'
60 new_user: 'Crează un utilizator' 62 new_user: 'Crează un utilizator'
63 # reset: 'Reset area'
61 form: 64 form:
62 save: 'Salvează' 65 save: 'Salvează'
63 form_settings: 66 form_settings:
@@ -65,12 +68,8 @@ config:
65 items_per_page_label: 'Articole pe pagină' 68 items_per_page_label: 'Articole pe pagină'
66 language_label: 'Limbă' 69 language_label: 'Limbă'
67 reading_speed: 70 reading_speed:
68 # label: 'Reading speed' 71 # label: 'Reading speed (words per minute)'
69 # help_message: 'You can use online tools to estimate your reading speed:' 72 # help_message: 'You can use online tools to estimate your reading speed:'
70 # 100_word: 'I read ~100 words per minute'
71 # 200_word: 'I read ~200 words per minute'
72 # 300_word: 'I read ~300 words per minute'
73 # 400_word: 'I read ~400 words per minute'
74 action_mark_as_read: 73 action_mark_as_read:
75 # label: 'Where do you want to be redirected to after marking an article as read?' 74 # label: 'Where do you want to be redirected to after marking an article as read?'
76 # redirect_homepage: 'To the homepage' 75 # redirect_homepage: 'To the homepage'
@@ -83,25 +82,35 @@ 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." 82 # 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." 83 # 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." 84 # help_pocket_consumer_key: "Required for Pocket import. You can create it in your Pocket account."
86 form_rss: 85 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.' 86 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' 87 token_label: 'RSS-Token'
89 no_token: 'Fără token' 88 no_token: 'Fără token'
90 token_create: 'Crează-ți token' 89 token_create: 'Crează-ți token'
91 token_reset: 'Resetează-ți token-ul' 90 token_reset: 'Resetează-ți token-ul'
92 rss_links: 'Link-uri RSS' 91 # token_revoke: 'Revoke the token'
93 rss_link: 92 feed_links: 'Link-uri RSS'
93 feed_link:
94 unread: 'Unread' 94 unread: 'Unread'
95 starred: 'Starred' 95 starred: 'Starred'
96 archive: 'Archived' 96 archive: 'Archived'
97 # all: 'All' 97 # all: 'All'
98 rss_limit: 'Limită RSS' 98 feed_limit: 'Limită RSS'
99 form_user: 99 form_user:
100 # two_factor_description: "Enabling two factor authentication means you'll receive an email with a code on every new untrusted connexion" 100 # two_factor_description: "Enabling two factor authentication means you'll receive an email with a code 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 # login_label: 'Login (can not be changed)'
101 name_label: 'Nume' 102 name_label: 'Nume'
102 email_label: 'E-mail' 103 email_label: 'E-mail'
103 # twoFactorAuthentication_label: 'Two factor authentication' 104 two_factor:
104 # help_twoFactorAuthentication: "If you enable 2FA, each time you want to login to wallabag, you'll receive a code by email." 105 # emailTwoFactor_label: 'Using email (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.
@@ -127,6 +136,15 @@ config:
127 # edit_rule_label: 'edit' 136 # edit_rule_label: 'edit'
128 # rule_label: 'Rule' 137 # rule_label: 'Rule'
129 # tags_label: 'Tags' 138 # tags_label: 'Tags'
139 # card:
140 # new_tagging_rule: Create a tagging rule
141 # import_tagging_rules: Import tagging rules
142 # import_tagging_rules_detail: You have to select the JSON file you previously exported.
143 # export_tagging_rules: Export tagging rules
144 # export_tagging_rules_detail: This will download a JSON file that you can use to import tagging rules elsewhere or to backup them.
145 # file_label: JSON file
146 # import_submit: Import
147 # export: Export
130 # faq: 148 # faq:
131 # title: 'FAQ' 149 # title: 'FAQ'
132 # tagging_rules_definition_title: 'What does « tagging rules » mean?' 150 # tagging_rules_definition_title: 'What does « tagging rules » mean?'
@@ -159,6 +177,15 @@ config:
159 # and: 'One rule AND another' 177 # 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>' 178 # 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>' 179 # notmatches: 'Tests that a <i>subject</i> doesn''t match match a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
180 otp:
181 # page_title: Two-factor authentication
182 # app:
183 # 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.
184 # two_factor_code_description_2: 'You can scan that QR Code with your app:'
185 # 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:'
186 # two_factor_code_description_4: 'Test an OTP code from your configured app:'
187 # cancel: Cancel
188 # enable: Enable
162 189
163entry: 190entry:
164 # default_title: 'Title of the entry' 191 # default_title: 'Title of the entry'
@@ -237,7 +264,7 @@ entry:
237 # provided_by: 'Provided by' 264 # provided_by: 'Provided by'
238 new: 265 new:
239 page_title: 'Salvează un nou articol' 266 page_title: 'Salvează un nou articol'
240 placeholder: 'http://website.com' 267 placeholder: 'https://website.ro'
241 form_new: 268 form_new:
242 url_label: Url 269 url_label: Url
243 search: 270 search:
@@ -253,6 +280,11 @@ entry:
253 confirm: 280 confirm:
254 # delete: "Are you sure you want to remove that article?" 281 # delete: "Are you sure you want to remove that article?"
255 # delete_tag: "Are you sure you want to remove that tag from that article?" 282 # delete_tag: "Are you sure you want to remove that tag from that article?"
283 metadata:
284 # reading_time: "Estimated reading time"
285 # reading_time_minutes_short: "%readingTime% min"
286 # address: "Address"
287 # added_on: "Added on"
256 288
257about: 289about:
258 page_title: 'Despre' 290 page_title: 'Despre'
@@ -348,7 +380,7 @@ quickstart:
348 # title: 'Configure the application' 380 # title: 'Configure the application'
349 # description: 'In order to have an application which suits you, have a look into the configuration of wallabag.' 381 # description: 'In order to have an application which suits you, have a look into the configuration of wallabag.'
350 # language: 'Change language and design' 382 # language: 'Change language and design'
351 # rss: 'Enable RSS feeds' 383 # feed: 'Enable RSS feeds'
352 # tagging_rules: 'Write rules to automatically tag your articles' 384 # tagging_rules: 'Write rules to automatically tag your articles'
353 # admin: 385 # admin:
354 # title: 'Administration' 386 # title: 'Administration'
@@ -396,12 +428,16 @@ tag:
396 list: 428 list:
397 # number_on_the_page: '{0} There is no tag.|{1} There is one tag.|]1,Inf[ There are %count% tags.' 429 # number_on_the_page: '{0} There is no tag.|{1} There is one tag.|]1,Inf[ There are %count% tags.'
398 # see_untagged_entries: 'See untagged entries' 430 # see_untagged_entries: 'See untagged entries'
431 # no_untagged_entries: 'There are no untagged entries.'
399 new: 432 new:
400 # add: 'Add' 433 # add: 'Add'
401 # placeholder: 'You can add several tags, separated by a comma.' 434 # placeholder: 'You can add several tags, separated by a comma.'
435 rename:
436 # placeholder: 'You can update tag name.'
402 437
403# export: 438export:
404# 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>' 439 # 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>'
440 # unknown: 'Unknown'
405 441
406import: 442import:
407 # page_title: 'Import' 443 # page_title: 'Import'
@@ -429,6 +465,9 @@ import:
429 # wallabag_v2: 465 # wallabag_v2:
430 # page_title: 'Import > Wallabag v2' 466 # page_title: 'Import > Wallabag v2'
431 # description: 'This importer will import all your wallabag v2 articles. Go to All articles, then, on the export sidebar, click on "JSON". You will have a "All articles.json" file.' 467 # description: 'This importer will import all your wallabag v2 articles. Go to All articles, then, on the export sidebar, click on "JSON". You will have a "All articles.json" file.'
468 # elcurator:
469 # page_title: 'Import > elCurator'
470 # description: 'This importer will import all your elCurator articles. Go to your preferences in your elCurator account and then, export your content. You will have a JSON file.'
432 # readability: 471 # readability:
433 # page_title: 'Import > Readability' 472 # page_title: 'Import > Readability'
434 # description: 'This importer will import all your Readability articles. On the tools (https://www.readability.com/tools/) page, click on "Export your data" in the "Data Export" section. You will received an email to download a json (which does not end with .json in fact).' 473 # description: 'This importer will import all your Readability articles. On the tools (https://www.readability.com/tools/) page, click on "Export your data" in the "Data Export" section. You will received an email to download a json (which does not end with .json in fact).'
@@ -482,6 +521,7 @@ developer:
482 # redirect_uris_label: 'Redirect URIs' 521 # redirect_uris_label: 'Redirect URIs'
483 # save_label: 'Create a new client' 522 # save_label: 'Create a new client'
484 # action_back: 'Back' 523 # action_back: 'Back'
524 # copy_to_clipboard: Copy
485 # client_parameter: 525 # client_parameter:
486 # page_title: 'API clients management > Client parameters' 526 # page_title: 'API clients management > Client parameters'
487 # page_description: 'Here are your client parameters.' 527 # page_description: 'Here are your client parameters.'
@@ -523,7 +563,8 @@ user:
523 email_label: 'E-mail' 563 email_label: 'E-mail'
524 # enabled_label: 'Enabled' 564 # enabled_label: 'Enabled'
525 # last_login_label: 'Last login' 565 # last_login_label: 'Last login'
526 # twofactor_label: Two factor authentication 566 # twofactor_email_label: Two factor authentication by email
567 # twofactor_google_label: Two factor authentication by OTP app
527 # save: Save 568 # save: Save
528 # delete: Delete 569 # delete: Delete
529 # delete_confirm: Are you sure? 570 # delete_confirm: Are you sure?
@@ -544,7 +585,7 @@ site_credential:
544 # create_new_one: Create a new credential 585 # create_new_one: Create a new credential
545 # form: 586 # form:
546 # username_label: 'Username' 587 # username_label: 'Username'
547 # host_label: 'Host' 588 # host_label: 'Host (subdomain.example.org, .example.org, etc.)'
548 # password_label: 'Password' 589 # password_label: 'Password'
549 # save: Save 590 # save: Save
550 # delete: Delete 591 # delete: Delete
@@ -561,14 +602,18 @@ flashes:
561 password_updated: 'Parolă actualizată' 602 password_updated: 'Parolă actualizată'
562 password_not_updated_demo: "In demonstration mode, you can't change password for this user." 603 password_not_updated_demo: "In demonstration mode, you can't change password for this user."
563 user_updated: 'Informație actualizată' 604 user_updated: 'Informație actualizată'
564 rss_updated: 'Informație RSS actualizată' 605 feed_updated: 'Informație RSS actualizată'
565 # tagging_rules_updated: 'Tagging rules updated' 606 # tagging_rules_updated: 'Tagging rules updated'
566 # tagging_rules_deleted: 'Tagging rule deleted' 607 # tagging_rules_deleted: 'Tagging rule deleted'
567 # rss_token_updated: 'RSS token updated' 608 # feed_token_updated: 'RSS token updated'
609 # feed_token_revoked: 'RSS token revoked'
568 # annotations_reset: Annotations reset 610 # annotations_reset: Annotations reset
569 # tags_reset: Tags reset 611 # tags_reset: Tags reset
570 # entries_reset: Entries reset 612 # entries_reset: Entries reset
571 # archived_reset: Archived entries deleted 613 # archived_reset: Archived entries deleted
614 # otp_enabled: Two-factor authentication enabled
615 # tagging_rules_imported: Tagging rules imported
616 # tagging_rules_not_imported: Error while importing tagging rules
572 entry: 617 entry:
573 notice: 618 notice:
574 # entry_already_saved: 'Entry already saved on %date%' 619 # entry_already_saved: 'Entry already saved on %date%'
@@ -582,9 +627,11 @@ flashes:
582 entry_starred: 'Articol adăugat la favorite' 627 entry_starred: 'Articol adăugat la favorite'
583 entry_unstarred: 'Articol șters de la favorite' 628 entry_unstarred: 'Articol șters de la favorite'
584 entry_deleted: 'Articol șters' 629 entry_deleted: 'Articol șters'
630 # no_random_entry: 'No article with these criterias was found'
585 tag: 631 tag:
586 notice: 632 notice:
587 # tag_added: 'Tag added' 633 # tag_added: 'Tag added'
634 # tag_renamed: 'Tag renamed'
588 import: 635 import:
589 notice: 636 notice:
590 # failed: 'Import failed, please try again.' 637 # 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 5f210c93..7d45df1c 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.ru.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.ru.yml
@@ -32,10 +32,13 @@ menu:
32 save_link: 'Сохранить ссылку' 32 save_link: 'Сохранить ссылку'
33 back_to_unread: 'Назад к непрочитанным записям' 33 back_to_unread: 'Назад к непрочитанным записям'
34 users_management: 'Управление пользователями' 34 users_management: 'Управление пользователями'
35 site_credentials: 'Site credentials'
36 quickstart: "Быстрый старт"
35 top: 37 top:
36 add_new_entry: 'Добавить новую запись' 38 add_new_entry: 'Добавить новую запись'
37 search: 'Поиск' 39 search: 'Поиск'
38 filter_entries: 'Фильтр записей' 40 filter_entries: 'Фильтр записей'
41 # random_entry: Jump to a random entry from that list
39 export: 'Экспорт' 42 export: 'Экспорт'
40 search_form: 43 search_form:
41 input_label: 'Введите текст для поиска' 44 input_label: 'Введите текст для поиска'
@@ -52,11 +55,12 @@ config:
52 page_title: 'Настройки' 55 page_title: 'Настройки'
53 tab_menu: 56 tab_menu:
54 settings: 'Настройки' 57 settings: 'Настройки'
55 rss: 'RSS' 58 feed: 'RSS'
56 user_info: 'Информация о пользователе' 59 user_info: 'Информация о пользователе'
57 password: 'Пароль' 60 password: 'Пароль'
58 rules: 'Правила настройки простановки тегов' 61 rules: 'Правила настройки простановки тегов'
59 new_user: 'Добавить пользователя' 62 new_user: 'Добавить пользователя'
63 reset: 'Сброс данных'
60 form: 64 form:
61 save: 'Сохранить' 65 save: 'Сохранить'
62 form_settings: 66 form_settings:
@@ -64,41 +68,49 @@ config:
64 items_per_page_label: 'Записей на странице' 68 items_per_page_label: 'Записей на странице'
65 language_label: 'Язык' 69 language_label: 'Язык'
66 reading_speed: 70 reading_speed:
67 label: 'Скорость чтения' 71 label: 'Скорость чтения (слов в минуту)'
68 help_message: 'Вы можете использовать онлайн-инструменты для оценки скорости чтения:' 72 help_message: 'Вы можете использовать онлайн-инструменты для оценки скорости чтения:'
69 100_word: 'Я читаю ~100 слов в минуту'
70 200_word: 'Я читаю ~200 слов в минуту'
71 300_word: 'Я читаю ~300 слов в минуту'
72 400_word: 'Я читаю ~400 слов в минуту'
73 action_mark_as_read: 73 action_mark_as_read:
74 label: 'Куда Вы хотите быть перенаправлены, после пометки записи, как прочитанная?' 74 label: 'Куда Вы хотите быть перенаправлены, после пометки записи, как прочитанная?'
75 redirect_homepage: 'На домашнюю страницу' 75 redirect_homepage: 'На домашнюю страницу'
76 redirect_current_page: 'На текущую страницу' 76 redirect_current_page: 'На текущую страницу'
77 pocket_consumer_key_label: "Ключ от Pocket для импорта контента" 77 pocket_consumer_key_label: "Ключ от Pocket для импорта контента"
78 android_configuration: "Настройте Ваше Android приложение" 78 android_configuration: "Настройте Ваше Android приложение"
79 # android_instruction: "Touch here to prefill your Android application"
79 help_theme: "wallabag настраиваемый, здесь Вы можете выбрать тему." 80 help_theme: "wallabag настраиваемый, здесь Вы можете выбрать тему."
80 help_items_per_page: "Вы можете выбрать количество отображаемых записей на странице." 81 help_items_per_page: "Вы можете выбрать количество отображаемых записей на странице."
81 help_reading_speed: "wallabag посчитает сколько времени занимает чтение каждой записи. Вы можете определить здесь, как быстро вы читаете. wallabag пересчитает время чтения для каждой записи." 82 help_reading_speed: "wallabag посчитает сколько времени занимает чтение каждой записи. Вы можете определить здесь, как быстро вы читаете. wallabag пересчитает время чтения для каждой записи."
82 help_language: "Вы можете изменить язык интерфейса wallabag." 83 help_language: "Вы можете изменить язык интерфейса wallabag."
83 help_pocket_consumer_key: "Обязательно для импорта из Pocket. Вы можете создать это в Вашем аккаунте на Pocket." 84 help_pocket_consumer_key: "Обязательно для импорта из Pocket. Вы можете создать это в Вашем аккаунте на Pocket."
84 form_rss: 85 form_feed:
85 description: 'RSS фид созданный с помощью wallabag позволяет читать Ваши записи через Ваш любимый RSS агрегатор. Для начала Вам потребуется создать ключ.' 86 description: 'RSS фид созданный с помощью wallabag позволяет читать Ваши записи через Ваш любимый RSS агрегатор. Для начала Вам потребуется создать ключ.'
86 token_label: 'RSS ключ' 87 token_label: 'RSS ключ'
87 no_token: 'Ключ не задан' 88 no_token: 'Ключ не задан'
88 token_create: 'Создать ключ' 89 token_create: 'Создать ключ'
89 token_reset: 'Пересоздать ключ' 90 token_reset: 'Пересоздать ключ'
90 rss_links: 'ссылка на RSS' 91 # token_revoke: 'Revoke the token'
91 rss_link: 92 feed_links: 'ссылка на RSS'
93 feed_link:
92 unread: 'непрочитанные' 94 unread: 'непрочитанные'
93 starred: 'помеченные' 95 starred: 'помеченные'
94 archive: 'архивные' 96 archive: 'архивные'
95 rss_limit: 'Количество записей в фиде' 97 # all: 'All'
98 feed_limit: 'Количество записей в фиде'
96 form_user: 99 form_user:
97 two_factor_description: "Включить двухфакторную аутентификацию, Вы получите сообщение на указанный email с кодом, при каждом новом непроверенном подключении." 100 # 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 # login_label: 'Login (can not be changed)'
98 name_label: 'Имя' 102 name_label: 'Имя'
99 email_label: 'Email' 103 email_label: 'Email'
100 twoFactorAuthentication_label: 'Двухфакторная аутентификация' 104 two_factor:
101 help_twoFactorAuthentication: "Если Вы включите двухфакторную аутентификацию, то Вы будете получать код на указанный ранее email, каждый раз при входе в wallabag." 105 # emailTwoFactor_label: 'Using email (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
102 delete: 114 delete:
103 title: "Удалить мой аккаунт (или опасная зона)" 115 title: "Удалить мой аккаунт (или опасная зона)"
104 description: "Если Вы удалите ваш аккаунт, ВСЕ ваши записи, теги и другие данные, будут БЕЗВОЗВРАТНО удалены (операция не может быть отменена после). Затем Вы выйдете из системы." 116 description: "Если Вы удалите ваш аккаунт, ВСЕ ваши записи, теги и другие данные, будут БЕЗВОЗВРАТНО удалены (операция не может быть отменена после). Затем Вы выйдете из системы."
@@ -110,6 +122,7 @@ config:
110 annotations: "Удалить все аннотации" 122 annotations: "Удалить все аннотации"
111 tags: "Удалить все теги" 123 tags: "Удалить все теги"
112 entries: "Удалить все записи" 124 entries: "Удалить все записи"
125 # archived: Remove ALL archived entries
113 confirm: "Вы уверены? (Данные будут БЕЗВОЗВРАТНО удалены, эти действия необратимы)" 126 confirm: "Вы уверены? (Данные будут БЕЗВОЗВРАТНО удалены, эти действия необратимы)"
114 form_password: 127 form_password:
115 description: "Здесь Вы можете поменять своя пароль. Ваш пароль должен быть длиннее 8 символов." 128 description: "Здесь Вы можете поменять своя пароль. Ваш пароль должен быть длиннее 8 символов."
@@ -123,6 +136,15 @@ config:
123 edit_rule_label: 'изменить' 136 edit_rule_label: 'изменить'
124 rule_label: 'Правило' 137 rule_label: 'Правило'
125 tags_label: 'теги' 138 tags_label: 'теги'
139 # card:
140 # new_tagging_rule: Create a tagging rule
141 # import_tagging_rules: Import tagging rules
142 # import_tagging_rules_detail: You have to select the JSON file you previously exported.
143 # export_tagging_rules: Export tagging rules
144 # export_tagging_rules_detail: This will download a JSON file that you can use to import tagging rules elsewhere or to backup them.
145 # file_label: JSON file
146 # import_submit: Import
147 # export: Export
126 faq: 148 faq:
127 title: 'FAQ' 149 title: 'FAQ'
128 tagging_rules_definition_title: 'Что значит "правило тегирования"?' 150 tagging_rules_definition_title: 'Что значит "правило тегирования"?'
@@ -154,6 +176,16 @@ config:
154 or: 'Одно правило ИЛИ другое' 176 or: 'Одно правило ИЛИ другое'
155 and: 'Одно правило И другое' 177 and: 'Одно правило И другое'
156 matches: 'Тесты, в которых <i> тема </i> соответствует <i> поиску </i> (без учета регистра). Пример: <code> title matches "футбол" </code>' 178 matches: 'Тесты, в которых <i> тема </i> соответствует <i> поиску </i> (без учета регистра). Пример: <code> title matches "футбол" </code>'
179 # notmatches: 'Tests that a <i>subject</i> doesn''t match match a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
180 otp:
181 # page_title: Two-factor authentication
182 # app:
183 # 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.
184 # two_factor_code_description_2: 'You can scan that QR Code with your app:'
185 # 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:'
186 # two_factor_code_description_4: 'Test an OTP code from your configured app:'
187 # cancel: Cancel
188 # enable: Enable
157 189
158entry: 190entry:
159 default_title: 'Название записи' 191 default_title: 'Название записи'
@@ -165,6 +197,7 @@ entry:
165 filtered_tags: 'Отфильтрованные по тегу:' 197 filtered_tags: 'Отфильтрованные по тегу:'
166 filtered_search: 'Отфильтрованные по поиску:' 198 filtered_search: 'Отфильтрованные по поиску:'
167 untagged: 'Записи без тегов' 199 untagged: 'Записи без тегов'
200 # all: 'All entries'
168 list: 201 list:
169 number_on_the_page: '{0} Записей не обнаружено.|{1} Одна запись.|]1,Inf[ Найдено %count% записей.' 202 number_on_the_page: '{0} Записей не обнаружено.|{1} Одна запись.|]1,Inf[ Найдено %count% записей.'
170 reading_time: 'расчетное время чтения' 203 reading_time: 'расчетное время чтения'
@@ -186,6 +219,8 @@ entry:
186 unread_label: 'Непрочитанная' 219 unread_label: 'Непрочитанная'
187 preview_picture_label: 'Есть картинка предварительного просмотра' 220 preview_picture_label: 'Есть картинка предварительного просмотра'
188 preview_picture_help: 'Картинка предварительного просмотра' 221 preview_picture_help: 'Картинка предварительного просмотра'
222 # is_public_label: 'Has a public link'
223 # is_public_help: 'Public link'
189 language_label: 'Язык' 224 language_label: 'Язык'
190 http_status_label: 'статус HTTP' 225 http_status_label: 'статус HTTP'
191 reading_time: 226 reading_time:
@@ -224,10 +259,12 @@ entry:
224 original_article: 'оригинал' 259 original_article: 'оригинал'
225 annotations_on_the_entry: '{0} Нет аннотации|{1} Одна аннотация|]1,Inf[ %count% аннотаций' 260 annotations_on_the_entry: '{0} Нет аннотации|{1} Одна аннотация|]1,Inf[ %count% аннотаций'
226 created_at: 'Дата создания' 261 created_at: 'Дата создания'
262 # published_at: 'Publication date'
263 # published_by: 'Published by'
227 # provided_by: 'Provided by' 264 # provided_by: 'Provided by'
228 new: 265 new:
229 page_title: 'Сохранить новую запись' 266 page_title: 'Сохранить новую запись'
230 placeholder: 'http://website.com' 267 placeholder: 'https://website.ru'
231 form_new: 268 form_new:
232 url_label: Ссылка 269 url_label: Ссылка
233 search: 270 search:
@@ -237,10 +274,17 @@ entry:
237 title_label: 'Название' 274 title_label: 'Название'
238 url_label: 'Ссылка' 275 url_label: 'Ссылка'
239 # origin_url_label: 'Origin url (from where you found that entry)' 276 # origin_url_label: 'Origin url (from where you found that entry)'
240 is_public_label: 'Публичная'
241 save_label: 'Сохранить' 277 save_label: 'Сохранить'
242 public: 278 public:
243 shared_by_wallabag: "Запись была опубликована <a href='%wallabag_instance%'>wallabag</a>" 279 shared_by_wallabag: "Запись была опубликована <a href='%wallabag_instance%'>wallabag</a>"
280 confirm:
281 # delete: "Are you sure you want to remove that article?"
282 # delete_tag: "Are you sure you want to remove that tag from that article?"
283 metadata:
284 # reading_time: "Estimated reading time"
285 # reading_time_minutes_short: "%readingTime% min"
286 # address: "Address"
287 # added_on: "Added on"
244 288
245about: 289about:
246 page_title: 'О' 290 page_title: 'О'
@@ -336,7 +380,7 @@ quickstart:
336 title: 'Настроить приложение' 380 title: 'Настроить приложение'
337 description: 'Чтобы иметь приложение, которое вам подходит, ознакомьтесь с конфигурацией wallabag.' 381 description: 'Чтобы иметь приложение, которое вам подходит, ознакомьтесь с конфигурацией wallabag.'
338 language: 'Выбрать язык и дизайн' 382 language: 'Выбрать язык и дизайн'
339 rss: 'Включить RSS фид' 383 feed: 'Включить RSS фид'
340 tagging_rules: 'Создать правило для автоматической установки тегов' 384 tagging_rules: 'Создать правило для автоматической установки тегов'
341 admin: 385 admin:
342 title: 'Администрирование' 386 title: 'Администрирование'
@@ -384,9 +428,16 @@ tag:
384 list: 428 list:
385 number_on_the_page: '{0} Теги не найдены.|{1} Найден один тег.|]1,Inf[ Найдено %count% тегов.' 429 number_on_the_page: '{0} Теги не найдены.|{1} Найден один тег.|]1,Inf[ Найдено %count% тегов.'
386 see_untagged_entries: 'Просмотреть записи без тегов' 430 see_untagged_entries: 'Просмотреть записи без тегов'
431 # no_untagged_entries: 'There are no untagged entries.'
387 new: 432 new:
388 add: 'Добавить' 433 add: 'Добавить'
389 placeholder: 'Вы можете добавить несколько тегов, разделенных запятой.' 434 placeholder: 'Вы можете добавить несколько тегов, разделенных запятой.'
435 rename:
436 # placeholder: 'You can update tag name.'
437
438export:
439 # 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>'
440 # unknown: 'Unknown'
390 441
391import: 442import:
392 page_title: 'Импорт' 443 page_title: 'Импорт'
@@ -414,6 +465,9 @@ import:
414 wallabag_v2: 465 wallabag_v2:
415 page_title: 'Импорт > Wallabag v2' 466 page_title: 'Импорт > Wallabag v2'
416 description: 'Функция импорта добавит все ваши записи wallabag v2. Перейдите ко всем статьям, затем на боковой панели экспорта нажмите "JSON". У вас появится файл со всеми записями "All articles.json".' 467 description: 'Функция импорта добавит все ваши записи wallabag v2. Перейдите ко всем статьям, затем на боковой панели экспорта нажмите "JSON". У вас появится файл со всеми записями "All articles.json".'
468 # elcurator:
469 # page_title: 'Import > elCurator'
470 # description: 'This importer will import all your elCurator articles. Go to your preferences in your elCurator account and then, export your content. You will have a JSON file.'
417 readability: 471 readability:
418 page_title: 'Импорт > Readability' 472 page_title: 'Импорт > Readability'
419 description: 'Функция импорта добавит все ваши записи для чтения. На странице инструментов (https://www.readability.com/tools/) нажмите "Экспорт ваших данных" в разделе "Экспорт данных". Вы получите электронное письмо для загрузки json (что не заканчивается только .json файлом).' 473 description: 'Функция импорта добавит все ваши записи для чтения. На странице инструментов (https://www.readability.com/tools/) нажмите "Экспорт ваших данных" в разделе "Экспорт данных". Вы получите электронное письмо для загрузки json (что не заканчивается только .json файлом).'
@@ -467,6 +521,7 @@ developer:
467 redirect_uris_label: 'Ссылка перенаправления (опционально)' 521 redirect_uris_label: 'Ссылка перенаправления (опционально)'
468 save_label: 'Создать нового клиента' 522 save_label: 'Создать нового клиента'
469 action_back: 'Назад' 523 action_back: 'Назад'
524 # copy_to_clipboard: Copy
470 client_parameter: 525 client_parameter:
471 page_title: 'Управление клиентским API > Параметры клиента' 526 page_title: 'Управление клиентским API > Параметры клиента'
472 page_description: 'Здесь ваши параметры клиента.' 527 page_description: 'Здесь ваши параметры клиента.'
@@ -508,11 +563,34 @@ user:
508 email_label: 'Email' 563 email_label: 'Email'
509 enabled_label: 'Включить' 564 enabled_label: 'Включить'
510 last_login_label: 'Последний вход' 565 last_login_label: 'Последний вход'
511 twofactor_label: "Двухфакторная аутентификация" 566 # twofactor_email_label: Two factor authentication by email
567 # twofactor_google_label: Two factor authentication by OTP app
512 save: "Сохранить" 568 save: "Сохранить"
513 delete: "Удалить" 569 delete: "Удалить"
514 delete_confirm: "Вы уверены?" 570 delete_confirm: "Вы уверены?"
515 back_to_list: "Назад к списку" 571 back_to_list: "Назад к списку"
572 search:
573 # placeholder: Filter by login or email
574
575site_credential:
576 # page_title: Site credentials management
577 # new_site_credential: Create a credential
578 # edit_site_credential: Edit an existing credential
579 # description: "Here you can manage all credentials for sites which required them (create, edit and delete), like a paywall, an authentication, etc."
580 # list:
581 # actions: Actions
582 # edit_action: Edit
583 # yes: Yes
584 # no: No
585 # create_new_one: Create a new credential
586 # form:
587 # username_label: 'Login'
588 # host_label: 'Host (subdomain.example.org, .example.org, etc.)'
589 # password_label: 'Password'
590 # save: Save
591 # delete: Delete
592 # delete_confirm: Are you sure?
593 # back_to_list: Back to list
516 594
517error: 595error:
518 page_title: "Произошла ошибка" 596 page_title: "Произошла ошибка"
@@ -524,13 +602,18 @@ flashes:
524 password_updated: 'Пароль обновлен' 602 password_updated: 'Пароль обновлен'
525 password_not_updated_demo: "В режиме демонстрации нельзя изменять пароль для этого пользователя." 603 password_not_updated_demo: "В режиме демонстрации нельзя изменять пароль для этого пользователя."
526 user_updated: 'Информация обновлена' 604 user_updated: 'Информация обновлена'
527 rss_updated: 'RSS информация обновлена' 605 feed_updated: 'RSS информация обновлена'
528 tagging_rules_updated: 'Правила тегировния обновлены' 606 tagging_rules_updated: 'Правила тегировния обновлены'
529 tagging_rules_deleted: 'Правила тегировния удалены' 607 tagging_rules_deleted: 'Правила тегировния удалены'
530 rss_token_updated: 'RSS ключ обновлен' 608 feed_token_updated: 'RSS ключ обновлен'
609 # feed_token_revoked: 'RSS token revoked'
531 annotations_reset: "Аннотации сброшены" 610 annotations_reset: "Аннотации сброшены"
532 tags_reset: "Теги сброшены" 611 tags_reset: "Теги сброшены"
533 entries_reset: "Записи сброшены" 612 entries_reset: "Записи сброшены"
613 # archived_reset: Archived entries deleted
614 # otp_enabled: Two-factor authentication enabled
615 # tagging_rules_imported: Tagging rules imported
616 # tagging_rules_not_imported: Error while importing tagging rules
534 entry: 617 entry:
535 notice: 618 notice:
536 entry_already_saved: 'Запись была сохранена ранее %date%' 619 entry_already_saved: 'Запись была сохранена ранее %date%'
@@ -544,9 +627,11 @@ flashes:
544 entry_starred: 'Запись помечена звездочкой' 627 entry_starred: 'Запись помечена звездочкой'
545 entry_unstarred: 'Пометка звездочкой у записи убрана' 628 entry_unstarred: 'Пометка звездочкой у записи убрана'
546 entry_deleted: 'Запись удалена' 629 entry_deleted: 'Запись удалена'
630 # no_random_entry: 'No article with these criterias was found'
547 tag: 631 tag:
548 notice: 632 notice:
549 tag_added: 'Тег добавлен' 633 tag_added: 'Тег добавлен'
634 # tag_renamed: 'Tag renamed'
550 import: 635 import:
551 notice: 636 notice:
552 failed: 'Во время импорта произошла ошибка, повторите попытку.' 637 failed: 'Во время импорта произошла ошибка, повторите попытку.'
@@ -564,4 +649,9 @@ flashes:
564 notice: 649 notice:
565 added: 'Пользователь "%username%" добавлен' 650 added: 'Пользователь "%username%" добавлен'
566 updated: 'Пользователь "%username%" обновлен' 651 updated: 'Пользователь "%username%" обновлен'
567 deleted: 'Пользователь "%username%" удален' \ No newline at end of file 652 deleted: 'Пользователь "%username%" удален'
653 site_credential:
654 notice:
655 # added: 'Site credential for "%host%" added'
656 # updated: 'Site credential for "%host%" updated'
657 # deleted: 'Site credential for "%host%" deleted'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.th.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.th.yml
index 9d22f90d..03f8fa9a 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.th.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.th.yml
@@ -33,10 +33,12 @@ menu:
33 back_to_unread: 'กลับไปยังรายการที่ไม่ได้อ่าน' 33 back_to_unread: 'กลับไปยังรายการที่ไม่ได้อ่าน'
34 users_management: 'การจัดการผู้ใช้' 34 users_management: 'การจัดการผู้ใช้'
35 site_credentials: 'การรับรองไซต์' 35 site_credentials: 'การรับรองไซต์'
36 quickstart: "เริ่มแบบด่วน"
36 top: 37 top:
37 add_new_entry: 'เพิ่มรายการใหม่' 38 add_new_entry: 'เพิ่มรายการใหม่'
38 search: 'ค้นหา' 39 search: 'ค้นหา'
39 filter_entries: 'ตัวกรองรายการ' 40 filter_entries: 'ตัวกรองรายการ'
41 # random_entry: Jump to a random entry from that list
40 export: 'นำข้อมูลออก' 42 export: 'นำข้อมูลออก'
41 search_form: 43 search_form:
42 input_label: 'ค้นหาที่นี้' 44 input_label: 'ค้นหาที่นี้'
@@ -53,11 +55,12 @@ config:
53 page_title: 'กำหนดค่า' 55 page_title: 'กำหนดค่า'
54 tab_menu: 56 tab_menu:
55 settings: 'ตั้งค่า' 57 settings: 'ตั้งค่า'
56 rss: 'RSS' 58 feed: 'RSS'
57 user_info: 'ข้อมูลผู้ใช้' 59 user_info: 'ข้อมูลผู้ใช้'
58 password: 'รหัสผ่าน' 60 password: 'รหัสผ่าน'
59 rules: 'การแท็กข้อบังคับ' 61 rules: 'การแท็กข้อบังคับ'
60 new_user: 'เพิ่มผู้ใช้' 62 new_user: 'เพิ่มผู้ใช้'
63 reset: 'รีเซ็ตพื้นที่ '
61 form: 64 form:
62 save: 'บันทึก' 65 save: 'บันทึก'
63 form_settings: 66 form_settings:
@@ -65,12 +68,8 @@ config:
65 items_per_page_label: 'ไอเทมต่อหน้า' 68 items_per_page_label: 'ไอเทมต่อหน้า'
66 language_label: 'ภาษา' 69 language_label: 'ภาษา'
67 reading_speed: 70 reading_speed:
68 label: 'การอ่านแบบด่วน' 71 label: 'การอ่านแบบด่วน (คำต่อนาที)'
69 help_message: 'คุณสามารถใช้เครื่องมือออนไลน์เพื่อประเมินการอ่านแบบด่วน:' 72 help_message: 'คุณสามารถใช้เครื่องมือออนไลน์เพื่อประเมินการอ่านแบบด่วน:'
70 100_word: 'ฉันอ่าน ~100 คำต่อนาที'
71 200_word: 'ฉันอ่าน ~200 คำต่อนาท'
72 300_word: 'ฉันอ่าน ~300 คำต่อนาท'
73 400_word: 'ฉันอ่าน ~400 คำต่อนาท'
74 action_mark_as_read: 73 action_mark_as_read:
75 label: 'คุณต้องการเปลี่ยนทิศทางหลังจากระบุเครื่องหมายรายการอ่านที่ไหน?' 74 label: 'คุณต้องการเปลี่ยนทิศทางหลังจากระบุเครื่องหมายรายการอ่านที่ไหน?'
76 redirect_homepage: 'ไปยังโฮมเพจ' 75 redirect_homepage: 'ไปยังโฮมเพจ'
@@ -83,25 +82,35 @@ config:
83 help_reading_speed: "wallabag จะคำนวณเวลาการอ่านในแต่ละรายการซึ่งคุณสามารถกำหนดได้ที่นี้,ต้องขอบคุณรายการนี้,หากคุณเป็นนักอ่านที่เร็วหรือช้า wallabag จะทำการคำนวณเวลาที่อ่านใหม่ในแต่ละรายการ" 82 help_reading_speed: "wallabag จะคำนวณเวลาการอ่านในแต่ละรายการซึ่งคุณสามารถกำหนดได้ที่นี้,ต้องขอบคุณรายการนี้,หากคุณเป็นนักอ่านที่เร็วหรือช้า wallabag จะทำการคำนวณเวลาที่อ่านใหม่ในแต่ละรายการ"
84 help_language: "คุณสามารถเปลี่ยภาษาของ wallabag interface ได้" 83 help_language: "คุณสามารถเปลี่ยภาษาของ wallabag interface ได้"
85 help_pocket_consumer_key: "การ้องขอการเก็บการนำข้อมูลเข้า คุณสามารถสร้างบัญชีการเก็บของคุณ" 84 help_pocket_consumer_key: "การ้องขอการเก็บการนำข้อมูลเข้า คุณสามารถสร้างบัญชีการเก็บของคุณ"
86 form_rss: 85 form_feed:
87 description: 'RSS จะเก็บเงื่อนไขโดย wallabag ต้องยอมรับการอ่านรายการของคุณกับผู้อ่านที่ชอบ RSS คุณต้องทำเครื่องหมายก่อน' 86 description: 'RSS จะเก็บเงื่อนไขโดย wallabag ต้องยอมรับการอ่านรายการของคุณกับผู้อ่านที่ชอบ RSS คุณต้องทำเครื่องหมายก่อน'
88 token_label: 'เครื่องหมาย RSS' 87 token_label: 'เครื่องหมาย RSS'
89 no_token: 'ไม่มีเครื่องหมาย' 88 no_token: 'ไม่มีเครื่องหมาย'
90 token_create: 'สร้างเครื่องหมาย' 89 token_create: 'สร้างเครื่องหมาย'
91 token_reset: 'ทำเครื่องหมาย' 90 token_reset: 'ทำเครื่องหมาย'
92 rss_links: 'ลิงค์ RSS' 91 # token_revoke: 'Revoke the token'
93 rss_link: 92 feed_links: 'ลิงค์ RSS'
93 feed_link:
94 unread: 'ยังไมได้่อ่าน' 94 unread: 'ยังไมได้่อ่าน'
95 starred: 'ทำการแสดง' 95 starred: 'ทำการแสดง'
96 archive: 'เอกสาร' 96 archive: 'เอกสาร'
97 all: 'ทั้งหมด' 97 all: 'ทั้งหมด'
98 rss_limit: 'จำนวนไอเทมที่เก็บ' 98 feed_limit: 'จำนวนไอเทมที่เก็บ'
99 form_user: 99 form_user:
100 two_factor_description: "การเปิดใช้งาน two factor authentication คือคุณจะต้องได้รับอีเมลกับ code ที่ยังไม่ตรวจสอบในการเชื่อมต่อ" 100 # 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 # login_label: 'Login (can not be changed)'
101 name_label: 'ชื่อ' 102 name_label: 'ชื่อ'
102 email_label: 'อีเมล' 103 email_label: 'อีเมล'
103 twoFactorAuthentication_label: 'Two factor authentication' 104 two_factor:
104 help_twoFactorAuthentication: "ถ้าคุณเปิด 2FA, ในแต่ละช่วงเวลาที่คุณต้องการลงชื่อเข้าใช wallabag, คุณจะต้องได้รับ code จากอีเมล" 105 # emailTwoFactor_label: 'Using email (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: ลบบัญชีของฉัน (โซนที่เป็นภัย!) 115 title: ลบบัญชีของฉัน (โซนที่เป็นภัย!)
107 description: ถ้าคุณลบบัญชีของคุณIf , รายการทั้งหมดของคุณ, แท็กทั้งหมดของคุณ, หมายเหตุทั้งหมดของคุณและบัญชีของคุณจะถูกลบอย่างถาวร (มันไม่สามารถยกเลิกได้) คุณจะต้องลงชื่อออก 116 description: ถ้าคุณลบบัญชีของคุณIf , รายการทั้งหมดของคุณ, แท็กทั้งหมดของคุณ, หมายเหตุทั้งหมดของคุณและบัญชีของคุณจะถูกลบอย่างถาวร (มันไม่สามารถยกเลิกได้) คุณจะต้องลงชื่อออก
@@ -127,6 +136,15 @@ config:
127 edit_rule_label: 'ปรับแก้' 136 edit_rule_label: 'ปรับแก้'
128 rule_label: 'ข้อบังคับ' 137 rule_label: 'ข้อบังคับ'
129 tags_label: 'แท็ก' 138 tags_label: 'แท็ก'
139 # card:
140 # new_tagging_rule: Create a tagging rule
141 # import_tagging_rules: Import tagging rules
142 # import_tagging_rules_detail: You have to select the JSON file you previously exported.
143 # export_tagging_rules: Export tagging rules
144 # export_tagging_rules_detail: This will download a JSON file that you can use to import tagging rules elsewhere or to backup them.
145 # file_label: JSON file
146 # import_submit: Import
147 # export: Export
130 faq: 148 faq:
131 title: 'FAQ' 149 title: 'FAQ'
132 tagging_rules_definition_title: 'ข้อบังคับการแท็กคืออะไร?' 150 tagging_rules_definition_title: 'ข้อบังคับการแท็กคืออะไร?'
@@ -159,6 +177,15 @@ config:
159 and: 'หนึ่งข้อบังคับและอื่นๆ' 177 and: 'หนึ่งข้อบังคับและอื่นๆ'
160 matches: 'ทดสอบว่า <i>เรื่อง</i> นี้ตรงกับ <i>การต้นหา</i> (กรณีไม่ทราบ).<br />ตัวอย่าง: <code>หัวข้อที่ตรงกับ "football"</code>' 178 matches: 'ทดสอบว่า <i>เรื่อง</i> นี้ตรงกับ <i>การต้นหา</i> (กรณีไม่ทราบ).<br />ตัวอย่าง: <code>หัวข้อที่ตรงกับ "football"</code>'
161 notmatches: 'ทดสอบว่า <i>เรื่อง</i> นี้ไม่ตรงกับ <i>การต้นหา</i> (กรณีไม่ทราบ).<br />ตัวอย่าง: <code>หัวข้อทีไม่ตรงกับ "football"</code>' 179 notmatches: 'ทดสอบว่า <i>เรื่อง</i> นี้ไม่ตรงกับ <i>การต้นหา</i> (กรณีไม่ทราบ).<br />ตัวอย่าง: <code>หัวข้อทีไม่ตรงกับ "football"</code>'
180 otp:
181 # page_title: Two-factor authentication
182 # app:
183 # 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.
184 # two_factor_code_description_2: 'You can scan that QR Code with your app:'
185 # 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:'
186 # two_factor_code_description_4: 'Test an OTP code from your configured app:'
187 # cancel: Cancel
188 # enable: Enable
162 189
163entry: 190entry:
164 default_title: 'หัวข้อรายการ' 191 default_title: 'หัวข้อรายการ'
@@ -234,9 +261,10 @@ entry:
234 created_at: 'วันที่สร้าง' 261 created_at: 'วันที่สร้าง'
235 published_at: 'วันที่ประกาศ' 262 published_at: 'วันที่ประกาศ'
236 published_by: 'ประกาศโดย' 263 published_by: 'ประกาศโดย'
264 # provided_by: 'Provided by'
237 new: 265 new:
238 page_title: 'บันทึกรายการใหม่' 266 page_title: 'บันทึกรายการใหม่'
239 placeholder: 'http://website.com' 267 placeholder: 'https://website.th'
240 form_new: 268 form_new:
241 url_label: Url 269 url_label: Url
242 search: 270 search:
@@ -245,12 +273,18 @@ entry:
245 page_title: 'แก้ไขรายการ' 273 page_title: 'แก้ไขรายการ'
246 title_label: 'หัวข้อ' 274 title_label: 'หัวข้อ'
247 url_label: 'Url' 275 url_label: 'Url'
276 # origin_url_label: 'Origin url (from where you found that entry)'
248 save_label: 'บันทึก' 277 save_label: 'บันทึก'
249 public: 278 public:
250 shared_by_wallabag: "บทความนี้จะมีการแชร์โดย %username% กับ <a href='%wallabag_instance%'>wallabag</a>" 279 shared_by_wallabag: "บทความนี้จะมีการแชร์โดย %username% กับ <a href='%wallabag_instance%'>wallabag</a>"
251 confirm: 280 confirm:
252 delete: "คุณแน่ใจหรือไม่ว่าคุณต้องการลบบทความนี้?" 281 delete: "คุณแน่ใจหรือไม่ว่าคุณต้องการลบบทความนี้?"
253 delete_tag: "คุณแน่ใจหรือไม่ว่าคุณต้องการลบแท็กจากบทความนี้?" 282 delete_tag: "คุณแน่ใจหรือไม่ว่าคุณต้องการลบแท็กจากบทความนี้?"
283 metadata:
284 # reading_time: "Estimated reading time"
285 # reading_time_minutes_short: "%readingTime% min"
286 # address: "Address"
287 # added_on: "Added on"
254 288
255about: 289about:
256 page_title: 'เกี่ยวกับ' 290 page_title: 'เกี่ยวกับ'
@@ -346,7 +380,7 @@ quickstart:
346 title: 'กำหนดค่าแอพพลิเคชั่น' 380 title: 'กำหนดค่าแอพพลิเคชั่น'
347 description: 'ภายใน order จะมี application suit ของคุณ, จะมองหาองค์ประกอบของ wallabag' 381 description: 'ภายใน order จะมี application suit ของคุณ, จะมองหาองค์ประกอบของ wallabag'
348 language: 'เปลี่ยนภาษาและออกแบบ' 382 language: 'เปลี่ยนภาษาและออกแบบ'
349 rss: 'เปิดใช้ RSS' 383 feed: 'เปิดใช้ RSS'
350 tagging_rules: 'เขียนข้อบังคับการแท็กอัตโนมัติของบทความของคุณ' 384 tagging_rules: 'เขียนข้อบังคับการแท็กอัตโนมัติของบทความของคุณ'
351 admin: 385 admin:
352 title: 'ผู้ดูแลระบบ' 386 title: 'ผู้ดูแลระบบ'
@@ -394,12 +428,16 @@ tag:
394 list: 428 list:
395 number_on_the_page: '{0} ไม่มีการแท็ก|{1} มีหนึ่งแท็ก|]1,Inf[ มี %count% แท็ก' 429 number_on_the_page: '{0} ไม่มีการแท็ก|{1} มีหนึ่งแท็ก|]1,Inf[ มี %count% แท็ก'
396 see_untagged_entries: 'พบรายการที่ไม่ได้แท็ก' 430 see_untagged_entries: 'พบรายการที่ไม่ได้แท็ก'
431 # no_untagged_entries: 'There are no untagged entries.'
397 new: 432 new:
398 add: 'เพิ่ม' 433 add: 'เพิ่ม'
399 placeholder: 'คุณสามารถเพิ่มได้หลายแท็ก, จากการแบ่งโดย comma' 434 placeholder: 'คุณสามารถเพิ่มได้หลายแท็ก, จากการแบ่งโดย comma'
435 rename:
436 # placeholder: 'You can update tag name.'
400 437
401export: 438export:
402 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>' 439 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>'
440 # unknown: 'Unknown'
403 441
404import: 442import:
405 page_title: 'นำข้อมูลเช้า' 443 page_title: 'นำข้อมูลเช้า'
@@ -427,6 +465,9 @@ import:
427 wallabag_v2: 465 wallabag_v2:
428 page_title: 'นำเข้าข้อมูล > Wallabag v2' 466 page_title: 'นำเข้าข้อมูล > Wallabag v2'
429 description: 'สำหรับผู้นำเข้าข้อมูลจะ import บทความ wallabag v2 ทั้งหมดของคุณ ไปยังบทความทั้งหมด, ดังนั้น, บน export sidebar, click ที่ "JSON" คุณจะมีไฟล์ "All articles.json"' 467 description: 'สำหรับผู้นำเข้าข้อมูลจะ import บทความ wallabag v2 ทั้งหมดของคุณ ไปยังบทความทั้งหมด, ดังนั้น, บน export sidebar, click ที่ "JSON" คุณจะมีไฟล์ "All articles.json"'
468 # elcurator:
469 # page_title: 'Import > elCurator'
470 # description: 'This importer will import all your elCurator articles. Go to your preferences in your elCurator account and then, export your content. You will have a JSON file.'
430 readability: 471 readability:
431 page_title: 'นำเข้าข้อมูล > Readability' 472 page_title: 'นำเข้าข้อมูล > Readability'
432 description: 'สำหรับผู้นำเข้าข้อมูลจะ import บทความ Readability ทั้งหมดของคุณ ไปที่เครื่องมือ (https://www.readability.com/tools/) ของหน้านั้น, click ที่ "Export your data" ในส่วน "Data Export" คุณจะได้รับ email ไป download json (which does not end with .json in fact).' 473 description: 'สำหรับผู้นำเข้าข้อมูลจะ import บทความ Readability ทั้งหมดของคุณ ไปที่เครื่องมือ (https://www.readability.com/tools/) ของหน้านั้น, click ที่ "Export your data" ในส่วน "Data Export" คุณจะได้รับ email ไป download json (which does not end with .json in fact).'
@@ -480,6 +521,7 @@ developer:
480 redirect_uris_label: 'เส้นทางใหม่ของ URIs (ให้เลือกได้)' 521 redirect_uris_label: 'เส้นทางใหม่ของ URIs (ให้เลือกได้)'
481 save_label: 'สร่้างลูกข่ายใหม' 522 save_label: 'สร่้างลูกข่ายใหม'
482 action_back: 'กลับ' 523 action_back: 'กลับ'
524 # copy_to_clipboard: Copy
483 client_parameter: 525 client_parameter:
484 page_title: 'การจัดการลูกข่ายของ API > พารามิเตอร์ของลูกข่าย' 526 page_title: 'การจัดการลูกข่ายของ API > พารามิเตอร์ของลูกข่าย'
485 page_description: 'ที่นี้เป็นพารามิเตอร์ของลูกข่ายของคุณ' 527 page_description: 'ที่นี้เป็นพารามิเตอร์ของลูกข่ายของคุณ'
@@ -521,7 +563,8 @@ user:
521 email_label: 'อีเมล' 563 email_label: 'อีเมล'
522 enabled_label: 'เปิดใช้งาน' 564 enabled_label: 'เปิดใช้งาน'
523 last_login_label: 'ลงชื้อเข้าใช้ครั้งสุดท้าย' 565 last_login_label: 'ลงชื้อเข้าใช้ครั้งสุดท้าย'
524 twofactor_label: Two factor authentication 566 # twofactor_email_label: Two factor authentication by email
567 # twofactor_google_label: Two factor authentication by OTP app
525 save: บันทึก 568 save: บันทึก
526 delete: ลบ 569 delete: ลบ
527 delete_confirm: ตุณแน่ใจหรือไม่? 570 delete_confirm: ตุณแน่ใจหรือไม่?
@@ -542,7 +585,7 @@ site_credential:
542 create_new_one: สร้างข้อมูลส่วนตัวใหม่ 585 create_new_one: สร้างข้อมูลส่วนตัวใหม่
543 form: 586 form:
544 username_label: 'ชื่อผู้ใช้' 587 username_label: 'ชื่อผู้ใช้'
545 host_label: 'โฮส' 588 host_label: 'โฮส (subdomain.example.org, .example.org, etc.)'
546 password_label: 'รหัสผ่าน' 589 password_label: 'รหัสผ่าน'
547 save: บันทึก 590 save: บันทึก
548 delete: ลบ 591 delete: ลบ
@@ -559,14 +602,18 @@ flashes:
559 password_updated: 'อัปเดตรหัสผ่าน' 602 password_updated: 'อัปเดตรหัสผ่าน'
560 password_not_updated_demo: "In demonstration mode, you can't change password for this user." 603 password_not_updated_demo: "In demonstration mode, you can't change password for this user."
561 user_updated: 'อัปเดตข้อมูล' 604 user_updated: 'อัปเดตข้อมูล'
562 rss_updated: 'อัปเดตข้อมูล RSS' 605 feed_updated: 'อัปเดตข้อมูล RSS'
563 tagging_rules_updated: 'อัปเดตการแท็กข้อบังคับ' 606 tagging_rules_updated: 'อัปเดตการแท็กข้อบังคับ'
564 tagging_rules_deleted: 'การลบข้อบังคับของแท็ก' 607 tagging_rules_deleted: 'การลบข้อบังคับของแท็ก'
565 rss_token_updated: 'อัปเดตเครื่องหมาย RSS ' 608 feed_token_updated: 'อัปเดตเครื่องหมาย RSS '
609 # feed_token_revoked: 'RSS token revoked'
566 annotations_reset: รีเซ็ตหมายเหตุ 610 annotations_reset: รีเซ็ตหมายเหตุ
567 tags_reset: รีเซ็ตแท็ก 611 tags_reset: รีเซ็ตแท็ก
568 entries_reset: รีเซ็ตรายการ 612 entries_reset: รีเซ็ตรายการ
569 archived_reset: การลบเอกสารของรายการ 613 archived_reset: การลบเอกสารของรายการ
614 # otp_enabled: Two-factor authentication enabled
615 # tagging_rules_imported: Tagging rules imported
616 # tagging_rules_not_imported: Error while importing tagging rules
570 entry: 617 entry:
571 notice: 618 notice:
572 entry_already_saved: 'รายการพร้อมบันทึกที่ %date%' 619 entry_already_saved: 'รายการพร้อมบันทึกที่ %date%'
@@ -580,9 +627,11 @@ flashes:
580 entry_starred: 'รายการที่แสดง' 627 entry_starred: 'รายการที่แสดง'
581 entry_unstarred: 'รายการที่ไม่ได้แสดง' 628 entry_unstarred: 'รายการที่ไม่ได้แสดง'
582 entry_deleted: 'รายการที่ถูกลบ' 629 entry_deleted: 'รายการที่ถูกลบ'
630 # no_random_entry: 'No article with these criterias was found'
583 tag: 631 tag:
584 notice: 632 notice:
585 tag_added: 'แท็กที่เพิ่ม' 633 tag_added: 'แท็กที่เพิ่ม'
634 # tag_renamed: 'Tag renamed'
586 import: 635 import:
587 notice: 636 notice:
588 failed: 'นำข้อมูลเข้าล้มเหลว, ลองใหม่อีกครั้ง' 637 failed: 'นำข้อมูลเข้าล้มเหลว, ลองใหม่อีกครั้ง'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml
index 4c71f0b9..05cfbbfc 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml
@@ -33,10 +33,12 @@ menu:
33 back_to_unread: 'Okunmayan makalelere geri dön' 33 back_to_unread: 'Okunmayan makalelere geri dön'
34 # users_management: 'Users management' 34 # users_management: 'Users management'
35 # site_credentials: 'Site credentials' 35 # site_credentials: 'Site credentials'
36 quickstart: "Hızlı başlangıç"
36 top: 37 top:
37 add_new_entry: 'Yeni bir makale ekle' 38 add_new_entry: 'Yeni bir makale ekle'
38 search: 'Ara' 39 search: 'Ara'
39 filter_entries: 'Filtrele' 40 filter_entries: 'Filtrele'
41 # random_entry: Jump to a random entry from that list
40 export: 'Dışa Aktar' 42 export: 'Dışa Aktar'
41 search_form: 43 search_form:
42 input_label: 'Aramak istediğiniz herhangi bir şey yazın' 44 input_label: 'Aramak istediğiniz herhangi bir şey yazın'
@@ -53,11 +55,12 @@ config:
53 page_title: 'Yapılandırma' 55 page_title: 'Yapılandırma'
54 tab_menu: 56 tab_menu:
55 settings: 'Ayarlar' 57 settings: 'Ayarlar'
56 rss: 'RSS' 58 feed: 'RSS'
57 user_info: 'Kullanıcı bilgileri' 59 user_info: 'Kullanıcı bilgileri'
58 password: 'Şifre' 60 password: 'Şifre'
59 rules: 'Etiketleme kuralları' 61 rules: 'Etiketleme kuralları'
60 new_user: 'Bir kullanıcı ekle' 62 new_user: 'Bir kullanıcı ekle'
63 # reset: 'Reset area'
61 form: 64 form:
62 save: 'Kaydet' 65 save: 'Kaydet'
63 form_settings: 66 form_settings:
@@ -65,12 +68,8 @@ config:
65 items_per_page_label: 'Sayfa başına makale sayısı' 68 items_per_page_label: 'Sayfa başına makale sayısı'
66 language_label: 'Dil' 69 language_label: 'Dil'
67 reading_speed: 70 reading_speed:
68 # label: 'Reading speed' 71 # label: 'Reading speed (words per minute)'
69 # help_message: 'You can use online tools to estimate your reading speed:' 72 # help_message: 'You can use online tools to estimate your reading speed:'
70 # 100_word: 'I read ~100 words per minute'
71 # 200_word: 'I read ~200 words per minute'
72 # 300_word: 'I read ~300 words per minute'
73 # 400_word: 'I read ~400 words per minute'
74 action_mark_as_read: 73 action_mark_as_read:
75 # label: 'Where do you want to be redirected to after marking an article as read?' 74 # label: 'Where do you want to be redirected to after marking an article as read?'
76 # redirect_homepage: 'To the homepage' 75 # redirect_homepage: 'To the homepage'
@@ -83,25 +82,35 @@ 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." 82 # 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." 83 # 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." 84 # help_pocket_consumer_key: "Required for Pocket import. You can create it in your Pocket account."
86 form_rss: 85 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.' 86 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)' 87 token_label: 'RSS belirteci (token)'
89 no_token: 'Belirteç (token) yok' 88 no_token: 'Belirteç (token) yok'
90 token_create: 'Yeni belirteç (token) oluştur' 89 token_create: 'Yeni belirteç (token) oluştur'
91 token_reset: 'Belirteci (token) sıfırla' 90 token_reset: 'Belirteci (token) sıfırla'
92 rss_links: 'RSS akış bağlantıları' 91 # token_revoke: 'Revoke the token'
93 rss_link: 92 feed_links: 'RSS akış bağlantıları'
93 feed_link:
94 unread: 'Okunmayan' 94 unread: 'Okunmayan'
95 starred: 'Favoriler' 95 starred: 'Favoriler'
96 archive: 'Arşiv' 96 archive: 'Arşiv'
97 # all: 'All' 97 # all: 'All'
98 rss_limit: 'RSS içeriğinden talep edilecek makale limiti' 98 feed_limit: 'RSS içeriğinden talep edilecek makale limiti'
99 form_user: 99 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." 100 # 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 # login_label: 'Login (can not be changed)'
101 name_label: 'İsim' 102 name_label: 'İsim'
102 email_label: 'E-posta' 103 email_label: 'E-posta'
103 twoFactorAuthentication_label: 'İki adımlı doğrulama' 104 two_factor:
104 # help_twoFactorAuthentication: "If you enable 2FA, each time you want to login to wallabag, you'll receive a code by email." 105 # emailTwoFactor_label: 'Using email (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.
@@ -127,6 +136,15 @@ config:
127 # edit_rule_label: 'edit' 136 # edit_rule_label: 'edit'
128 rule_label: 'Kural' 137 rule_label: 'Kural'
129 tags_label: 'Etiketler' 138 tags_label: 'Etiketler'
139 # card:
140 # new_tagging_rule: Create a tagging rule
141 # import_tagging_rules: Import tagging rules
142 # import_tagging_rules_detail: You have to select the JSON file you previously exported.
143 # export_tagging_rules: Export tagging rules
144 # export_tagging_rules_detail: This will download a JSON file that you can use to import tagging rules elsewhere or to backup them.
145 # file_label: JSON file
146 # import_submit: Import
147 # export: Export
130 faq: 148 faq:
131 title: 'S.S.S.' 149 title: 'S.S.S.'
132 tagging_rules_definition_title: '« etiketleme kuralları » ne anlama geliyor?' 150 tagging_rules_definition_title: '« etiketleme kuralları » ne anlama geliyor?'
@@ -159,6 +177,15 @@ config:
159 and: 'Bir kural ve diğeri' 177 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>' 178 # 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>' 179 # notmatches: 'Tests that a <i>subject</i> doesn''t match match a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
180 otp:
181 # page_title: Two-factor authentication
182 # app:
183 # 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.
184 # two_factor_code_description_2: 'You can scan that QR Code with your app:'
185 # 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:'
186 # two_factor_code_description_4: 'Test an OTP code from your configured app:'
187 # cancel: Cancel
188 # enable: Enable
162 189
163entry: 190entry:
164 default_title: 'Makalenin başlığı' 191 default_title: 'Makalenin başlığı'
@@ -192,6 +219,8 @@ entry:
192 unread_label: 'Okunmayan' 219 unread_label: 'Okunmayan'
193 preview_picture_label: 'Resim önizlemesi varsa' 220 preview_picture_label: 'Resim önizlemesi varsa'
194 preview_picture_help: 'Resim önizlemesi' 221 preview_picture_help: 'Resim önizlemesi'
222 # is_public_label: 'Has a public link'
223 # is_public_help: 'Public link'
195 language_label: 'Dil' 224 language_label: 'Dil'
196 # http_status_label: 'HTTP status' 225 # http_status_label: 'HTTP status'
197 reading_time: 226 reading_time:
@@ -235,7 +264,7 @@ entry:
235 # provided_by: 'Provided by' 264 # provided_by: 'Provided by'
236 new: 265 new:
237 page_title: 'Yeni makaleyi kaydet' 266 page_title: 'Yeni makaleyi kaydet'
238 placeholder: 'http://website.com' 267 placeholder: 'https://website.tr'
239 form_new: 268 form_new:
240 url_label: Url 269 url_label: Url
241 search: 270 search:
@@ -251,6 +280,11 @@ entry:
251 confirm: 280 confirm:
252 # delete: "Are you sure you want to remove that article?" 281 # delete: "Are you sure you want to remove that article?"
253 # delete_tag: "Are you sure you want to remove that tag from that article?" 282 # delete_tag: "Are you sure you want to remove that tag from that article?"
283 metadata:
284 # reading_time: "Estimated reading time"
285 # reading_time_minutes_short: "%readingTime% min"
286 # address: "Address"
287 # added_on: "Added on"
254 288
255about: 289about:
256 page_title: 'Hakkımızda' 290 page_title: 'Hakkımızda'
@@ -346,7 +380,7 @@ quickstart:
346 title: 'Uygulamayı Yapılandırma' 380 title: 'Uygulamayı Yapılandırma'
347 # description: 'In order to have an application which suits you, have a look into the configuration of wallabag.' 381 # description: 'In order to have an application which suits you, have a look into the configuration of wallabag.'
348 language: 'Dili ve tasarımı değiştirme' 382 language: 'Dili ve tasarımı değiştirme'
349 rss: 'RSS akışını aktifleştirme' 383 feed: 'RSS akışını aktifleştirme'
350 # tagging_rules: 'Write rules to automatically tag your articles' 384 # tagging_rules: 'Write rules to automatically tag your articles'
351 admin: 385 admin:
352 # title: 'Administration' 386 # title: 'Administration'
@@ -394,12 +428,16 @@ tag:
394 list: 428 list:
395 number_on_the_page: '{0} Herhangi bir etiket yok.|{1} Burada bir adet etiket var.|]1,Inf[ Burada %count% adet etiket var.' 429 number_on_the_page: '{0} Herhangi bir etiket yok.|{1} Burada bir adet etiket var.|]1,Inf[ Burada %count% adet etiket var.'
396 # see_untagged_entries: 'See untagged entries' 430 # see_untagged_entries: 'See untagged entries'
431 # no_untagged_entries: 'There are no untagged entries.'
397 new: 432 new:
398 # add: 'Add' 433 # add: 'Add'
399 # placeholder: 'You can add several tags, separated by a comma.' 434 # placeholder: 'You can add several tags, separated by a comma.'
435 rename:
436 # placeholder: 'You can update tag name.'
400 437
401# export: 438export:
402# 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>' 439 # 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>'
440 # unknown: 'Unknown'
403 441
404import: 442import:
405 page_title: 'İçe Aktar' 443 page_title: 'İçe Aktar'
@@ -427,6 +465,9 @@ import:
427 wallabag_v2: 465 wallabag_v2:
428 page_title: 'İçe Aktar > Wallabag v2' 466 page_title: 'İçe Aktar > Wallabag v2'
429 # description: 'This importer will import all your wallabag v2 articles. Go to All articles, then, on the export sidebar, click on "JSON". You will have a "All articles.json" file.' 467 # description: 'This importer will import all your wallabag v2 articles. Go to All articles, then, on the export sidebar, click on "JSON". You will have a "All articles.json" file.'
468 # elcurator:
469 # page_title: 'Import > elCurator'
470 # description: 'This importer will import all your elCurator articles. Go to your preferences in your elCurator account and then, export your content. You will have a JSON file.'
430 readability: 471 readability:
431 page_title: 'İçe Aktar > Readability' 472 page_title: 'İçe Aktar > Readability'
432 # description: 'This importer will import all your Readability articles. On the tools (https://www.readability.com/tools/) page, click on "Export your data" in the "Data Export" section. You will received an email to download a json (which does not end with .json in fact).' 473 # description: 'This importer will import all your Readability articles. On the tools (https://www.readability.com/tools/) page, click on "Export your data" in the "Data Export" section. You will received an email to download a json (which does not end with .json in fact).'
@@ -480,6 +521,7 @@ developer:
480 # redirect_uris_label: 'Redirect URIs' 521 # redirect_uris_label: 'Redirect URIs'
481 # save_label: 'Create a new client' 522 # save_label: 'Create a new client'
482 # action_back: 'Back' 523 # action_back: 'Back'
524 # copy_to_clipboard: Copy
483 # client_parameter: 525 # client_parameter:
484 # page_title: 'API clients management > Client parameters' 526 # page_title: 'API clients management > Client parameters'
485 # page_description: 'Here are your client parameters.' 527 # page_description: 'Here are your client parameters.'
@@ -521,7 +563,8 @@ user:
521 email_label: 'E-posta' 563 email_label: 'E-posta'
522 # enabled_label: 'Enabled' 564 # enabled_label: 'Enabled'
523 # last_login_label: 'Last login' 565 # last_login_label: 'Last login'
524 # twofactor_label: Two factor authentication 566 # twofactor_email_label: Two factor authentication by email
567 # twofactor_google_label: Two factor authentication by OTP app
525 # save: Save 568 # save: Save
526 # delete: Delete 569 # delete: Delete
527 # delete_confirm: Are you sure? 570 # delete_confirm: Are you sure?
@@ -529,6 +572,26 @@ user:
529 search: 572 search:
530 # placeholder: Filter by username or email 573 # placeholder: Filter by username or email
531 574
575site_credential:
576 # page_title: Site credentials management
577 # new_site_credential: Create a credential
578 # edit_site_credential: Edit an existing credential
579 # description: "Here you can manage all credentials for sites which required them (create, edit and delete), like a paywall, an authentication, etc."
580 # list:
581 # actions: Actions
582 # edit_action: Edit
583 # yes: Yes
584 # no: No
585 # create_new_one: Create a new credential
586 # form:
587 # username_label: 'Login'
588 # host_label: 'Host (subdomain.example.org, .example.org, etc.)'
589 # password_label: 'Password'
590 # save: Save
591 # delete: Delete
592 # delete_confirm: Are you sure?
593 # back_to_list: Back to list
594
532error: 595error:
533 # page_title: An error occurred 596 # page_title: An error occurred
534 597
@@ -539,14 +602,18 @@ flashes:
539 password_updated: 'Şifre güncellendi' 602 password_updated: 'Şifre güncellendi'
540 password_not_updated_demo: "In demonstration mode, you can't change password for this user." 603 password_not_updated_demo: "In demonstration mode, you can't change password for this user."
541 user_updated: 'Bilgiler güncellendi' 604 user_updated: 'Bilgiler güncellendi'
542 rss_updated: 'RSS bilgiler güncellendi' 605 feed_updated: 'RSS bilgiler güncellendi'
543 tagging_rules_updated: 'Tagging rules updated' 606 # tagging_rules_updated: 'Tagging rules updated'
544 tagging_rules_deleted: 'Tagging rule deleted' 607 # tagging_rules_deleted: 'Tagging rule deleted'
545 rss_token_updated: 'RSS token updated' 608 # feed_token_updated: 'RSS token updated'
609 # feed_token_revoked: 'RSS token revoked'
546 # annotations_reset: Annotations reset 610 # annotations_reset: Annotations reset
547 # tags_reset: Tags reset 611 # tags_reset: Tags reset
548 # entries_reset: Entries reset 612 # entries_reset: Entries reset
549 # archived_reset: Archived entries deleted 613 # archived_reset: Archived entries deleted
614 # otp_enabled: Two-factor authentication enabled
615 # tagging_rules_imported: Tagging rules imported
616 # tagging_rules_not_imported: Error while importing tagging rules
550 entry: 617 entry:
551 notice: 618 notice:
552 entry_already_saved: 'Entry already saved on %date%' 619 entry_already_saved: 'Entry already saved on %date%'
@@ -560,9 +627,11 @@ flashes:
560 entry_starred: 'Makale favorilere eklendi' 627 entry_starred: 'Makale favorilere eklendi'
561 entry_unstarred: 'Makale favorilerden çıkartıldı' 628 entry_unstarred: 'Makale favorilerden çıkartıldı'
562 entry_deleted: 'Makale silindi' 629 entry_deleted: 'Makale silindi'
630 # no_random_entry: 'No article with these criterias was found'
563 tag: 631 tag:
564 notice: 632 notice:
565 tag_added: 'Etiket eklendi' 633 tag_added: 'Etiket eklendi'
634 # tag_renamed: 'Tag renamed'
566 import: 635 import:
567 notice: 636 notice:
568 # failed: 'Import failed, please try again.' 637 # 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..ea6575eb 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/validators.es.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/validators.es.yml
@@ -1,7 +1,7 @@
1validator: 1validator:
2 password_must_match: 'Las contraseñas no coinciden' 2 password_must_match: 'Las contraseñas no coinciden'
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 caracteres'
4 password_wrong_value: 'Entrada equivocada para su contraseña actual' 4 password_wrong_value: 'La contraseña actual es incorrecta'
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: 'La cita es demasiado larga. Debe tener {{ limit }} caracteres o menos.'
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..1d69af97 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: 'A citaçãpo é muito longa. Ela deve ter {{ limit }} caracteres ou menos.'
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/base.html.twig b/src/Wallabag/CoreBundle/Resources/views/base.html.twig
index aa388bcb..496b3fb6 100644
--- a/src/Wallabag/CoreBundle/Resources/views/base.html.twig
+++ b/src/Wallabag/CoreBundle/Resources/views/base.html.twig
@@ -1,13 +1,15 @@
1<!DOCTYPE html> 1<!DOCTYPE html>
2<!--[if lte IE 6]><html class="no-js ie6 ie67 ie678" lang="en"><![endif]--> 2{% set lang = app.request.locale|default('') -%}
3<!--[if lte IE 7]><html class="no-js ie7 ie67 ie678" lang="en"><![endif]--> 3<!--[if lte IE 6]><html class="no-js ie6 ie67 ie678"{% if lang is not empty %} lang="{{ lang }}"{% endif %}><![endif]-->
4<!--[if IE 8]><html class="no-js ie8 ie678" lang="en"><![endif]--> 4<!--[if lte IE 7]><html class="no-js ie7 ie67 ie678"{% if lang is not empty %} lang="{{ lang }}"{% endif %}><![endif]-->
5<!--[if gt IE 8]><html class="no-js" lang="en"><![endif]--> 5<!--[if IE 8]><html class="no-js ie8 ie678"{% if lang is not empty %} lang="{{ lang }}"{% endif %}><![endif]-->
6<html> 6<!--[if gt IE 8]><html class="no-js"{% if lang is not empty %} lang="{{ lang }}"{% endif %}><![endif]-->
7<html{% if lang is not empty %} lang="{{ lang }}"{% endif %}>
7 <head> 8 <head>
8 {% block head %} 9 {% block head %}
9 <meta name="viewport" content="initial-scale=1.0"> 10 <meta name="viewport" content="initial-scale=1.0">
10 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 11 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
12 <meta name="referrer" content="strict-origin-when-cross-origin">
11 <!--[if IE]> 13 <!--[if IE]>
12 <meta http-equiv="X-UA-Compatible" content="IE=10"> 14 <meta http-equiv="X-UA-Compatible" content="IE=10">
13 <![endif]--> 15 <![endif]-->
@@ -42,6 +44,7 @@
42 44
43 {% block css %} 45 {% block css %}
44 {% endblock %} 46 {% endblock %}
47 <link rel="stylesheet" href="{{ asset('custom.css') }}">
45 {% block scripts %} 48 {% block scripts %}
46 <script src="{{ asset('bundles/fosjsrouting/js/router.js') }}"></script> 49 <script src="{{ asset('bundles/fosjsrouting/js/router.js') }}"></script>
47 <script src="{{ path('fos_js_routing_js', { callback: 'fos.Router.setData' }) }}"></script> 50 <script src="{{ path('fos_js_routing_js', { callback: 'fos.Router.setData' }) }}"></script>
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..f719bea2 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,42 @@
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>
113 {% endif %}
114
115 {% if feed.token %}
116 – <a href="{{ path('generate_token') }}">{{ 'config.form_feed.token_reset'|trans }}</a>
117 – <a href="{{ path('revoke_token') }}">{{ 'config.form_feed.token_revoke'|trans }}</a>
118 {% else %}
119 – <a href="{{ path('generate_token') }}">{{ 'config.form_feed.token_create'|trans }}</a>
114 {% endif %} 120 {% endif %}
115
116 <a href="{{ path('generate_token') }}">
117 {% if rss.token %}
118 {{ 'config.form_rss.token_reset'|trans }}
119 {% else %}
120 {{ 'config.form_rss.token_create'|trans }}
121 {% endif %}
122 </a>
123 </div> 121 </div>
124 </fieldset> 122 </fieldset>
125 123
126 {% if rss.token %} 124 {% if feed.token %}
127 <fieldset class="w500p inline"> 125 <fieldset class="w500p inline">
128 <div class="row"> 126 <div class="row">
129 <label>{{ 'config.form_rss.rss_links'|trans }}</label> 127 <label>{{ 'config.form_feed.feed_links'|trans }}</label>
130 <ul> 128 <ul>
131 <li><a href="{{ path('unread_rss', {'username': rss.username, 'token': rss.token}) }}">{{ 'config.form_rss.rss_link.unread'|trans }}</a></li> 129 <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> 130 <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> 131 <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> 132 <li><a href="{{ path('all_feed', {'username': feed.username, 'token': feed.token}) }}">{{ 'config.form_feed.feed_link.all'|trans }}</a></li>
135 </ul> 133 </ul>
136 </div> 134 </div>
137 </fieldset> 135 </fieldset>
@@ -139,19 +137,25 @@
139 137
140 <fieldset class="w500p inline"> 138 <fieldset class="w500p inline">
141 <div class="row"> 139 <div class="row">
142 {{ form_label(form.rss.rss_limit) }} 140 {{ form_label(form.feed.feed_limit) }}
143 {{ form_errors(form.rss.rss_limit) }} 141 {{ form_errors(form.feed.feed_limit) }}
144 {{ form_widget(form.rss.rss_limit) }} 142 {{ form_widget(form.feed.feed_limit) }}
145 </div> 143 </div>
146 </fieldset> 144 </fieldset>
147 145
148 {{ form_rest(form.rss) }} 146 {{ form_rest(form.feed) }}
149 </form> 147 </form>
150 148
151 <h2>{{ 'config.tab_menu.user_info'|trans }}</h2> 149 <h2>{{ 'config.tab_menu.user_info'|trans }}</h2>
152 150
153 {{ form_start(form.user) }} 151 {{ form_start(form.user) }}
154 {{ form_errors(form.user) }} 152 {{ form_errors(form.user) }}
153 <fieldset class="w500p inline">
154 <div class="row">
155 <label>{{ 'config.form_user.login_label'|trans }}</label>
156 {{ app.user.username }}
157 </div>
158 </fieldset>
155 159
156 <fieldset class="w500p inline"> 160 <fieldset class="w500p inline">
157 <div class="row"> 161 <div class="row">
@@ -169,52 +173,41 @@
169 </div> 173 </div>
170 </fieldset> 174 </fieldset>
171 175
176 {{ form_widget(form.user.save) }}
177
172 {% if twofactor_auth %} 178 {% if twofactor_auth %}
179 <h5>{{ 'config.otp.page_title'|trans }}</h5>
180
173 <div class="row"> 181 <div class="row">
174 {{ 'config.form_user.two_factor_description'|trans }} 182 {{ 'config.form_user.two_factor_description'|trans }}
175 </div> 183 </div>
176 184
177 <fieldset class="w500p inline"> 185 <table>
178 <div class="row"> 186 <thead>
179 {{ form_label(form.user.twoFactorAuthentication) }} 187 <tr>
180 {{ form_errors(form.user.twoFactorAuthentication) }} 188 <th>{{ 'config.form_user.two_factor.table_method'|trans }}</th>
181 {{ form_widget(form.user.twoFactorAuthentication) }} 189 <th>{{ 'config.form_user.two_factor.table_state'|trans }}</th>
182 </div> 190 <th>{{ 'config.form_user.two_factor.table_action'|trans }}</th>
183 <a href="#" title="{{ 'config.form_user.help_twoFactorAuthentication'|trans }}"> 191 </tr>
184 <i class="material-icons">live_help</i> 192 </thead>
185 </a>
186 </fieldset>
187 {% endif %}
188 193
189 <h2>{{ 'config.reset.title'|trans }}</h2> 194 <tbody>
190 <fieldset class="w500p inline"> 195 <tr>
191 <p>{{ 'config.reset.description'|trans }}</p> 196 <td>{{ 'config.form_user.two_factor.emailTwoFactor_label'|trans }}</td>
192 <ul> 197 <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> 198 <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"> 199 </tr>
195 {{ 'config.reset.annotations'|trans }} 200 <tr>
196 </a> 201 <td>{{ 'config.form_user.two_factor.googleTwoFactor_label'|trans }}</td>
197 </li> 202 <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> 203 <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"> 204 </tr>
200 {{ 'config.reset.tags'|trans }} 205 </tbody>
201 </a> 206 </table>
202 </li> 207
203 <li> 208 {% 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 209
216 {{ form_widget(form.user._token) }} 210 {{ form_widget(form.user._token) }}
217 {{ form_widget(form.user.save) }}
218 </form> 211 </form>
219 212
220 {% if enabled_users > 1 %} 213 {% if enabled_users > 1 %}
@@ -277,7 +270,7 @@
277 {% endfor %} 270 {% endfor %}
278 </ul> 271 </ul>
279 272
280 {{ form_start(form.new_tagging_rule) }} 273 {{ form_start(form.new_tagging_rule) }}
281 {{ form_errors(form.new_tagging_rule) }} 274 {{ form_errors(form.new_tagging_rule) }}
282 275
283 <fieldset class="w500p inline"> 276 <fieldset class="w500p inline">
@@ -298,6 +291,34 @@
298 291
299 {{ form_rest(form.new_tagging_rule) }} 292 {{ form_rest(form.new_tagging_rule) }}
300 </form> 293 </form>
294
295 <div class="row">
296 <h3>{{ 'config.form_rules.card.import_tagging_rules'|trans }}</h3>
297 <p>{{ 'config.form_rules.card.import_tagging_rules_detail'|trans }}</p>
298 </div>
299
300 {{ form_start(form.import_tagging_rule) }}
301 {{ form_errors(form.import_tagging_rule) }}
302
303 <fieldset class="w500p inline">
304 <div class="row">
305 {{ form_label(form.import_tagging_rule.file) }}
306 {{ form_errors(form.import_tagging_rule.file) }}
307 {{ form_widget(form.import_tagging_rule.file) }}
308 </div>
309 </fieldset>
310
311 {{ form_rest(form.import_tagging_rule) }}
312 </form>
313
314 {% if app.user.config.taggingRules is not empty %}
315 <div class="row">
316 <h3>{{ 'config.form_rules.card.export_tagging_rules'|trans }}</h3>
317 <p>{{ 'config.form_rules.card.export_tagging_rules_detail'|trans }}</p>
318 <p><a href="{{ path('export_tagging_rule') }}" class="waves-effect waves-light btn">{{ 'config.form_rules.export'|trans }}</a></p>
319 </div>
320 {% endif %}
321
301 <div class="row"> 322 <div class="row">
302 <div class="input-field col s12"> 323 <div class="input-field col s12">
303 <h3>{{ 'config.form_rules.faq.title'|trans }}</h3> 324 <h3>{{ 'config.form_rules.faq.title'|trans }}</h3>
@@ -382,4 +403,31 @@
382 </table> 403 </table>
383 </div> 404 </div>
384 </div> 405 </div>
406
407 <h2>{{ 'config.reset.title'|trans }}</h2>
408 <fieldset class="w500p inline">
409 <p>{{ 'config.reset.description'|trans }}</p>
410 <ul>
411 <li>
412 <a href="{{ path('config_reset', { type: 'annotations'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
413 {{ 'config.reset.annotations'|trans }}
414 </a>
415 </li>
416 <li>
417 <a href="{{ path('config_reset', { type: 'tags'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
418 {{ 'config.reset.tags'|trans }}
419 </a>
420 </li>
421 <li>
422 <a href="{{ path('config_reset', { type: 'archived'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
423 {{ 'config.reset.archived'|trans }}
424 </a>
425 </li>
426 <li>
427 <a href="{{ path('config_reset', { type: 'entries'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
428 {{ 'config.reset.entries'|trans }}
429 </a>
430 </li>
431 </ul>
432 </fieldset>
385{% endblock %} 433{% 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..1d3685ae
--- /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">{{ "auth_code"|trans({}, 'SchebTwoFactorBundle') }}</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 cfc6644b..b747ed84 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>
@@ -40,7 +46,7 @@
40 <div id="entry-{{ entry.id|e }}" class="{% if listMode == 0 %}entry{% else %}listmode entry{% endif %}"> 46 <div id="entry-{{ entry.id|e }}" class="{% if listMode == 0 %}entry{% else %}listmode entry{% endif %}">
41 <h2><a href="{{ path('view', { 'id': entry.id }) }}" title="{{ entry.title|e|raw }}">{{ entry.title | striptags | truncate(80, true, '…') | default('entry.default_title'|trans) | raw }}</a></h2> 47 <h2><a href="{{ path('view', { 'id': entry.id }) }}" title="{{ entry.title|e|raw }}">{{ entry.title | striptags | truncate(80, true, '…') | default('entry.default_title'|trans) | raw }}</a></h2>
42 48
43 {% set readingTime = entry.readingTime / app.user.config.readingSpeed %} 49 {% set readingTime = entry.readingTime / app.user.config.readingSpeed * 200 %}
44 <div class="estimatedTime"> 50 <div class="estimatedTime">
45 <span class="tool reading-time"> 51 <span class="tool reading-time">
46 {% if readingTime > 0 %} 52 {% if readingTime > 0 %}
@@ -90,17 +96,14 @@
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>
99 {% 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 %} 102 {% 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 %}
100 {% if craue_setting('export_mobi') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'mobi', 'tag' : currentTag }) }}">MOBI</a></li>{% endif %} 103 {% if craue_setting('export_mobi') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'mobi', 'tag' : currentTag }) }}">MOBI</a></li>{% endif %}
101 {% if craue_setting('export_pdf') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'pdf', 'tag' : currentTag }) }}">PDF</a></li>{% endif %} 104 {% if craue_setting('export_pdf') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'pdf', 'tag' : currentTag }) }}">PDF</a></li>{% endif %}
102 {% if craue_setting('export_csv') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'json', 'tag' : currentTag }) }}">JSON</a></li>{% endif %} 105 {% if craue_setting('export_json') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'json', 'tag' : currentTag }) }}">JSON</a></li>{% endif %}
103 {% if craue_setting('export_json') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'csv', 'tag' : currentTag }) }}">CSV</a></li>{% endif %} 106 {% if craue_setting('export_csv') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'csv', 'tag' : currentTag }) }}">CSV</a></li>{% endif %}
104 {% if craue_setting('export_txt') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'txt', 'tag' : currentTag }) }}">TXT</a></li>{% endif %} 107 {% if craue_setting('export_txt') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'txt', 'tag' : currentTag }) }}">TXT</a></li>{% endif %}
105 {% if craue_setting('export_xml') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'xml', 'tag' : currentTag }) }}">XML</a></li>{% endif %} 108 {% if craue_setting('export_xml') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'xml', 'tag' : currentTag }) }}">XML</a></li>{% endif %}
106 </ul> 109 </ul>
@@ -114,6 +117,12 @@
114 <a href="javascript: void(null);" id="filter-form-close" class="close-button--popup close-button">&times;</a> 117 <a href="javascript: void(null);" id="filter-form-close" class="close-button--popup close-button">&times;</a>
115 118
116 <div id="filter-status" class="filter-group"> 119 <div id="filter-status" class="filter-group">
120 {% if currentRoute != 'untagged' and nbEntriesUntagged != 0 %}
121 <div class="">
122 <a href="{{ path('untagged') }}">{{ 'tag.list.see_untagged_entries'|trans }} ({{nbEntriesUntagged}})</a>
123 </div>
124 {% endif %}
125
117 <div class=""> 126 <div class="">
118 <label>{{ 'entry.filters.status_label'|trans }}</label> 127 <label>{{ 'entry.filters.status_label'|trans }}</label>
119 </div> 128 </div>
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Entry/entry.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Entry/entry.html.twig
index e7d42b3d..c2e69a27 100644
--- a/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Entry/entry.html.twig
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Entry/entry.html.twig
@@ -5,7 +5,7 @@
5{% block content %} 5{% block content %}
6 <div id="article"> 6 <div id="article">
7 <header class="mbm"> 7 <header class="mbm">
8 <h1>{{ entry.title|e|default('entry.default_title'|trans)|raw }} <a href="{{ path('edit', { 'id': entry.id }) }}" class="nostyle" title="{{ 'entry.view.edit_title'|trans }}">✎</a></h1> 8 <h1><span{% if entry.language is defined and entry.language is not null %} lang="{{ entry.getHTMLLanguage() }}"{% endif %}>{{ entry.title|e|default('entry.default_title'|trans)|raw }}</span> <a href="{{ path('edit', { 'id': entry.id }) }}" class="nostyle" title="{{ 'entry.view.edit_title'|trans }}">✎</a></h1>
9 </header> 9 </header>
10 10
11 <div id="article_toolbar"> 11 <div id="article_toolbar">
@@ -27,7 +27,7 @@
27 <li><a href="{{ path('share', {'id': entry.id }) }}" target="_blank" class="tool icon icon-eye" title="{{ 'entry.view.left_menu.public_link'|trans }}"><span>{{ 'entry.view.left_menu.public_link'|trans }}</span></a></li> 27 <li><a href="{{ path('share', {'id': entry.id }) }}" target="_blank" class="tool icon icon-eye" title="{{ 'entry.view.left_menu.public_link'|trans }}"><span>{{ 'entry.view.left_menu.public_link'|trans }}</span></a></li>
28 <li><a href="{{ path('delete_share', {'id': entry.id }) }}" class="tool icon icon-no-eye" title="{{ 'entry.view.left_menu.delete_public_link'|trans }}"><span>{{ 'entry.view.left_menu.delete_public_link'|trans }}</span></a></li> 28 <li><a href="{{ path('delete_share', {'id': entry.id }) }}" class="tool icon icon-no-eye" title="{{ 'entry.view.left_menu.delete_public_link'|trans }}"><span>{{ 'entry.view.left_menu.delete_public_link'|trans }}</span></a></li>
29 {% endif %} 29 {% endif %}
30 {% if craue_setting('share_twitter') %}<li><a href="https://twitter.com/home?status={{entry.title|url_encode}}%20{{ entry.url|url_encode }}%20via%20@wallabagapp" target="_blank" rel="noopener" class="tool twitter icon icon-twitter" title="Tweet"><span>Tweet</span></a></li>{% endif %} 30 {% if craue_setting('share_twitter') %}<li><a href="https://twitter.com/share?text={{entry.title|url_encode}}%20{{ entry.url|url_encode }}%20via%20@wallabagapp" target="_blank" rel="noopener" class="tool twitter icon icon-twitter" title="Tweet"><span>Tweet</span></a></li>{% endif %}
31 {% if craue_setting('share_mail') %}<li><a href="mailto:?subject={{ entry.title|url_encode }}&amp;body={{ entry.url|url_encode }}%20via%20@wallabagapp" class="tool email icon icon-mail" title="Email"><span>Email</span></a></li>{% endif %} 31 {% if craue_setting('share_mail') %}<li><a href="mailto:?subject={{ entry.title|url_encode }}&amp;body={{ entry.url|url_encode }}%20via%20@wallabagapp" class="tool email icon icon-mail" title="Email"><span>Email</span></a></li>{% endif %}
32 {% if craue_setting('share_shaarli') %}<li><a href="{{ craue_setting('shaarli_url') }}/index.php?post={{ entry.url|url_encode }}&amp;title={{ entry.title|url_encode }}&amp;tags={{ entry.tags|join(',')|url_encode }}{% if craue_setting('shaarli_share_origin_url') %}&amp;original_url={{ entry.originUrl|url_encode }}{% endif %}" target="_blank" rel="noopener" class="tool icon-image icon-image--shaarli" title="shaarli"><span>shaarli</span></a></li>{% endif %} 32 {% if craue_setting('share_shaarli') %}<li><a href="{{ craue_setting('shaarli_url') }}/index.php?post={{ entry.url|url_encode }}&amp;title={{ entry.title|url_encode }}&amp;tags={{ entry.tags|join(',')|url_encode }}{% if craue_setting('shaarli_share_origin_url') %}&amp;original_url={{ entry.originUrl|url_encode }}{% endif %}" target="_blank" rel="noopener" class="tool icon-image icon-image--shaarli" title="shaarli"><span>shaarli</span></a></li>{% endif %}
33 {% if craue_setting('share_scuttle') %}<li><a href="{{ craue_setting('scuttle_url') }}/bookmarks.php?action=add&amp;address={{ entry.url|url_encode }}&amp;title={{ entry.title|url_encode }}&amp;tags={{ entry.tags|join(',')|url_encode }}" target="_blank" rel="noopener" class="tool icon-image icon-image--scuttle" title="scuttle"><span>scuttle</span></a></li>{% endif %} 33 {% if craue_setting('share_scuttle') %}<li><a href="{{ craue_setting('scuttle_url') }}/bookmarks.php?action=add&amp;address={{ entry.url|url_encode }}&amp;title={{ entry.title|url_encode }}&amp;tags={{ entry.tags|join(',')|url_encode }}" target="_blank" rel="noopener" class="tool icon-image icon-image--scuttle" title="scuttle"><span>scuttle</span></a></li>{% endif %}
@@ -38,7 +38,7 @@
38 {% if craue_setting('export_epub') %}<li><a href="{{ path('export_entry', { 'id': entry.id, 'format': 'epub' }) }}" title="Generate ePub file">EPUB</a></li>{% endif %} 38 {% if craue_setting('export_epub') %}<li><a href="{{ path('export_entry', { 'id': entry.id, 'format': 'epub' }) }}" title="Generate ePub file">EPUB</a></li>{% endif %}
39 {% if craue_setting('export_mobi') %}<li><a href="{{ path('export_entry', { 'id': entry.id, 'format': 'mobi' }) }}" title="Generate Mobi file">MOBI</a></li>{% endif %} 39 {% if craue_setting('export_mobi') %}<li><a href="{{ path('export_entry', { 'id': entry.id, 'format': 'mobi' }) }}" title="Generate Mobi file">MOBI</a></li>{% endif %}
40 {% if craue_setting('export_pdf') %}<li><a href="{{ path('export_entry', { 'id': entry.id, 'format': 'pdf' }) }}" title="Generate PDF file">PDF</a></li>{% endif %} 40 {% if craue_setting('export_pdf') %}<li><a href="{{ path('export_entry', { 'id': entry.id, 'format': 'pdf' }) }}" title="Generate PDF file">PDF</a></li>{% endif %}
41 <li><a href="mailto:hello@wallabag.org?subject=Wrong%20display%20in%20wallabag&amp;body={{ entry.url|url_encode }}" title="{{ 'entry.view.left_menu.problem.label'|trans }}" class="tool bad-display icon icon-delete"><span>{{ 'entry.view.left_menu.problem.label'|trans }}</span></a></li> 41 <li><a href="mailto:siteconfig@wallabag.org?subject=Wrong%20display%20in%20wallabag&amp;body={{ entry.url|url_encode }}" title="{{ 'entry.view.left_menu.problem.label'|trans }}" class="tool bad-display icon icon-delete"><span>{{ 'entry.view.left_menu.problem.label'|trans }}</span></a></li>
42 </ul> 42 </ul>
43 </div> 43 </div>
44 44
@@ -62,7 +62,7 @@
62 {% endif %} 62 {% endif %}
63 63
64 <i class="tool icon icon-time"> 64 <i class="tool icon icon-time">
65 {% set readingTime = entry.readingTime / app.user.config.readingSpeed %} 65 {% set readingTime = entry.readingTime / app.user.config.readingSpeed * 200 %}
66 {% if readingTime > 0 %} 66 {% if readingTime > 0 %}
67 {{ 'entry.list.reading_time_minutes_short'|trans({'%readingTime%': readingTime|round}) }} 67 {{ 'entry.list.reading_time_minutes_short'|trans({'%readingTime%': readingTime|round}) }}
68 {% else %} 68 {% else %}
@@ -70,7 +70,7 @@
70 {% endif %} 70 {% endif %}
71 </i> 71 </i>
72 72
73 <span class="tool link"><i class="material-icons link">comment</i> {{ 'entry.view.annotations_on_the_entry'|transchoice(entry.annotations | length) }}</span> 73 <span class="tool link"><i class="material-icons">comment</i> {{ 'entry.view.annotations_on_the_entry'|transchoice(entry.annotations | length) }}</span>
74 74
75 {% if entry.originUrl is not empty %} 75 {% if entry.originUrl is not empty %}
76 <i class="material-icons" title="{{ 'entry.view.provided_by'|trans }}">launch</i> 76 <i class="material-icons" title="{{ 'entry.view.provided_by'|trans }}">launch</i>
@@ -96,7 +96,7 @@
96 </div> 96 </div>
97 </aside> 97 </aside>
98 </div> 98 </div>
99 <article> 99 <article{% if entry.language is defined and entry.language is not null %} lang="{{ entry.getHTMLLanguage() }}"{% endif %}>
100 {{ entry.content | raw }} 100 {{ entry.content | raw }}
101 </article> 101 </article>
102 </div> 102 </div>
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..aa17b842 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,15 +10,31 @@
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>
20 32
21 <div> 33 <div>
22 <a href="{{ path('untagged') }}">{{ 'tag.list.see_untagged_entries'|trans }}</a> 34 {% if nbEntriesUntagged == 0 %}
35 {{ 'tag.list.no_untagged_entries'|trans }}
36 {% else %}
37 <a href="{{ path('untagged') }}">{{ 'tag.list.see_untagged_entries'|trans }} ({{nbEntriesUntagged}})</a>
38 {% endif %}
23 </div> 39 </div>
24{% endblock %} 40{% endblock %}
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/baggy/layout.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/baggy/layout.html.twig
index 35a54daf..6b1e2bd7 100644
--- a/src/Wallabag/CoreBundle/Resources/views/themes/baggy/layout.html.twig
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/baggy/layout.html.twig
@@ -50,6 +50,7 @@
50 <li class="menu howto"><a href="{{ path('howto') }}">{{ 'menu.left.howto'|trans }}</a></li> 50 <li class="menu howto"><a href="{{ path('howto') }}">{{ 'menu.left.howto'|trans }}</a></li>
51 <li class="menu developer"><a href="{{ path('developer') }}">{{ 'menu.left.developer'|trans }}</a></li> 51 <li class="menu developer"><a href="{{ path('developer') }}">{{ 'menu.left.developer'|trans }}</a></li>
52 <li class="menu about"><a href="{{ path('about') }}">{{ 'footer.wallabag.about'|trans }}</a></li> 52 <li class="menu about"><a href="{{ path('about') }}">{{ 'footer.wallabag.about'|trans }}</a></li>
53 <li class="menu quickstart"><a href="{{ path('quickstart') }}">{{ 'menu.left.quickstart'|trans }}</a></li>
53 <li class="menu logout"><a class="icon icon-power" href="{{ path('fos_user_security_logout') }}">{{ 'menu.left.logout'|trans }}</a></li> 54 <li class="menu logout"><a class="icon icon-power" href="{{ path('fos_user_security_logout') }}">{{ 'menu.left.logout'|trans }}</a></li>
54 </ul> 55 </ul>
55{% endblock %} 56{% endblock %}
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/common/Developer/client_parameters.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/common/Developer/client_parameters.html.twig
index b498cceb..3a3ba0c9 100644
--- a/src/Wallabag/CoreBundle/Resources/views/themes/common/Developer/client_parameters.html.twig
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/common/Developer/client_parameters.html.twig
@@ -8,11 +8,29 @@
8 <div class="card-panel settings"> 8 <div class="card-panel settings">
9 <div class="row"> 9 <div class="row">
10 <p>{{ 'developer.client_parameter.page_description'|trans }}</p> 10 <p>{{ 'developer.client_parameter.page_description'|trans }}</p>
11 <ul> 11
12 <li>{{ 'developer.client_parameter.field_name'|trans }}: <strong><pre>{{ client_name }}</pre></strong></li> 12 <table class="striped">
13 <li>{{ 'developer.client_parameter.field_id'|trans }}: <strong><pre>{{ client_id }}</pre></strong></li> 13 <tr>
14 <li>{{ 'developer.client_parameter.field_secret'|trans }}: <strong><pre>{{ client_secret }}</pre></strong></li> 14 <td>{{ 'developer.client_parameter.field_name'|trans }}</td>
15 </ul> 15 <td><strong><code>{{ client_name }}</code></strong></td>
16 </tr>
17 <tr>
18 <td>{{ 'developer.client_parameter.field_id'|trans }}</td>
19 <td>
20 <strong><code>{{ client_id }}</code></strong>
21 <button class="btn" data-clipboard-text="{{ client_id }}">{{ 'developer.client.copy_to_clipboard'|trans }}</button>
22 </td>
23 </tr>
24 <tr>
25 <td>{{ 'developer.client_parameter.field_secret'|trans }}</td>
26 <td>
27 <strong><code>{{ client_secret }}</code></strong>
28 <button class="btn" data-clipboard-text="{{ client_secret }}">{{ 'developer.client.copy_to_clipboard'|trans }}</button>
29 </td>
30 </tr>
31 </table>
32
33 <br/>
16 34
17 <a href="{{ path('developer') }}" class="waves-effect waves-light grey btn">{{ 'developer.client_parameter.back'|trans }}</a> 35 <a href="{{ path('developer') }}" class="waves-effect waves-light grey btn">{{ 'developer.client_parameter.back'|trans }}</a>
18 <a href="{{ path('developer_howto_firstapp') }}" class="btn waves-effect waves-light">{{ 'developer.client_parameter.read_howto'|trans }}</a> 36 <a href="{{ path('developer_howto_firstapp') }}" class="btn waves-effect waves-light">{{ 'developer.client_parameter.read_howto'|trans }}</a>
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/common/Developer/howto_app.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/common/Developer/howto_app.html.twig
index acbc2c88..dcadfa49 100644
--- a/src/Wallabag/CoreBundle/Resources/views/themes/common/Developer/howto_app.html.twig
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/common/Developer/howto_app.html.twig
@@ -18,7 +18,7 @@
18 <p>{{ 'developer.howto.description.paragraph_3'|trans({'%link%': path('developer_create_client')})|raw }}</p> 18 <p>{{ 'developer.howto.description.paragraph_3'|trans({'%link%': path('developer_create_client')})|raw }}</p>
19 <p>{{ 'developer.howto.description.paragraph_4'|trans }}</p> 19 <p>{{ 'developer.howto.description.paragraph_4'|trans }}</p>
20 <p> 20 <p>
21 <pre><code class="language-bash">http POST http://v2.wallabag.org/oauth/v2/token \ 21 <pre><code class="language-bash">http POST https://app.wallabag.it/oauth/v2/token \
22 grant_type=password \ 22 grant_type=password \
23 client_id=12_5um6nz50ceg4088c0840wwc0kgg44g00kk84og044ggkscso0k \ 23 client_id=12_5um6nz50ceg4088c0840wwc0kgg44g00kk84og044ggkscso0k \
24 client_secret=3qd12zpeaxes8cwg8c0404g888co4wo8kc4gcw0occww8cgw4k \ 24 client_secret=3qd12zpeaxes8cwg8c0404g888co4wo8kc4gcw0occww8cgw4k \
@@ -47,7 +47,7 @@ X-Powered-By: PHP/5.5.9-1ubuntu4.13
47 </p> 47 </p>
48 <p>{{ 'developer.howto.description.paragraph_6'|trans }}</p> 48 <p>{{ 'developer.howto.description.paragraph_6'|trans }}</p>
49 <p> 49 <p>
50 <pre><code class="language-bash">http GET http://v2.wallabag.org/api/entries.json \ 50 <pre><code class="language-bash">http GET https://app.wallabag.it/api/entries.json \
51 "Authorization:Bearer ZWFjNjA3ZWMwYWVmYzRkYTBlMmQ3NTllYmVhOGJiZDE0ZTg1NjE4MjczOTVlNzM0ZTRlMWQ0MmRlMmYwNTk5Mw"</code></pre> 51 "Authorization:Bearer ZWFjNjA3ZWMwYWVmYzRkYTBlMmQ3NTllYmVhOGJiZDE0ZTg1NjE4MjczOTVlNzM0ZTRlMWQ0MmRlMmYwNTk5Mw"</code></pre>
52 </p> 52 </p>
53 <p>{{ 'developer.howto.description.paragraph_7'|trans }}</p> 53 <p>{{ 'developer.howto.description.paragraph_7'|trans }}</p>
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/common/Developer/index.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/common/Developer/index.html.twig
index be04cddb..b83bf96f 100644
--- a/src/Wallabag/CoreBundle/Resources/views/themes/common/Developer/index.html.twig
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/common/Developer/index.html.twig
@@ -33,11 +33,17 @@
33 <table class="striped"> 33 <table class="striped">
34 <tr> 34 <tr>
35 <td>{{ 'developer.existing_clients.field_id'|trans }}</td> 35 <td>{{ 'developer.existing_clients.field_id'|trans }}</td>
36 <td><strong><code>{{ client.clientId }}</code></strong></td> 36 <td>
37 <strong><code>{{ client.clientId }}</code></strong>
38 <button class="btn" data-clipboard-text="{{ client.clientId }}">{{ 'developer.client.copy_to_clipboard'|trans }}</button>
39 </td>
37 </tr> 40 </tr>
38 <tr> 41 <tr>
39 <td>{{ 'developer.existing_clients.field_secret'|trans }}</td> 42 <td>{{ 'developer.existing_clients.field_secret'|trans }}</td>
40 <td><strong><code>{{ client.secret }}</code></strong></td> 43 <td>
44 <strong><code>{{ client.secret }}</code></strong>
45 <button class="btn" data-clipboard-text="{{ client.secret }}">{{ 'developer.client.copy_to_clipboard'|trans }}</button>
46 </td>
41 </tr> 47 </tr>
42 <tr> 48 <tr>
43 <td>{{ 'developer.existing_clients.field_uris'|trans }}</td> 49 <td>{{ 'developer.existing_clients.field_uris'|trans }}</td>
@@ -48,9 +54,10 @@
48 <td><strong><code>{{ client.allowedGrantTypes|json_encode() }}</code></strong></td> 54 <td><strong><code>{{ client.allowedGrantTypes|json_encode() }}</code></strong></td>
49 </tr> 55 </tr>
50 </table> 56 </table>
57
58 <p>{{ 'developer.remove.warn_message_1'|trans({'%name%': client.name }) }}</p>
59 <p>{{ 'developer.remove.warn_message_2'|trans({'%name%': client.name }) }}</p>
51 <p> 60 <p>
52 {{ 'developer.remove.warn_message_1'|trans({'%name%': client.name }) }}<br/>
53 {{ 'developer.remove.warn_message_2'|trans({'%name%': client.name }) }}<br/>
54 <a class="waves-effect waves-light red btn" href="{{ path('developer_delete_client', {'id': client.id}) }}">{{ 'developer.remove.action'|trans({'%name%': client.name }) }}</a> 61 <a class="waves-effect waves-light red btn" href="{{ path('developer_delete_client', {'id': client.id}) }}">{{ 'developer.remove.action'|trans({'%name%': client.name }) }}</a>
55 </p> 62 </p>
56 </div> 63 </div>
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/Entry/share.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/common/Entry/share.html.twig
index e1c7aad9..4294a60d 100644
--- a/src/Wallabag/CoreBundle/Resources/views/themes/common/Entry/share.html.twig
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/common/Entry/share.html.twig
@@ -29,9 +29,6 @@
29 <h1>{{ entry.title|e|raw }}</h1> 29 <h1>{{ entry.title|e|raw }}</h1>
30 <a href="{{ entry.url|e }}" target="_blank" rel="noopener" title="{{ 'entry.view.original_article'|trans }} : {{ entry.title|e|raw }}" class="tool">{{ entry.domainName|removeWww }}</a> 30 <a href="{{ entry.url|e }}" target="_blank" rel="noopener" title="{{ 'entry.view.original_article'|trans }} : {{ entry.title|e|raw }}" class="tool">{{ entry.domainName|removeWww }}</a>
31 <p class="shared-by">{{ "entry.public.shared_by_wallabag"|trans({'%wallabag_instance%': url('homepage'), '%username%': entry.user.username})|raw }}.</p> 31 <p class="shared-by">{{ "entry.public.shared_by_wallabag"|trans({'%wallabag_instance%': url('homepage'), '%username%': entry.user.username})|raw }}.</p>
32 {% if entry.previewPicture is not null %}
33 <img class="preview" src="{{ entry.previewPicture }}" alt="{{ entry.title|striptags|e('html_attr') }}" />
34 {% endif %}
35 </header> 32 </header>
36 <article class="block"> 33 <article class="block">
37 {{ entry.content | raw }} 34 {{ entry.content | raw }}
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..d8e9694d 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,43 @@
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
144 {% if rss.token %} 144 {% if feed.token %}
145 {{ 'config.form_rss.token_reset'|trans }} 145 – <a href="{{ path('generate_token') }}">{{ 'config.form_feed.token_reset'|trans }}</a>
146 – <a href="{{ path('revoke_token') }}">{{ 'config.form_feed.token_revoke'|trans }}</a>
146 {% else %} 147 {% else %}
147 {{ 'config.form_rss.token_create'|trans }} 148 – <a href="{{ path('generate_token') }}">{{ 'config.form_feed.token_create'|trans }}</a>
148 {% endif %}</a> 149 {% endif %}
149 </div> 150 </div>
150 </div> 151 </div>
151 </div> 152 </div>
152 {% if rss.token %} 153 {% if feed.token %}
153 <div class="row"> 154 <div class="row">
154 <div class="col s12"> 155 <div class="col s12">
155 <h6 class="grey-text">{{ 'config.form_rss.rss_links'|trans }}</h6> 156 <h6 class="grey-text">{{ 'config.form_feed.feed_links'|trans }}</h6>
156 <ul> 157 <ul>
157 <li><a href="{{ path('unread_rss', {'username': rss.username, 'token': rss.token}) }}">{{ 'config.form_rss.rss_link.unread'|trans }}</a></li> 158 <li><a href="{{ path('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> 159 <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> 160 <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> 161 <li><a href="{{ path('all_feed', {'username': feed.username, 'token': feed.token}) }}">{{ 'config.form_feed.feed_link.all'|trans }}</a></li>
161 </ul> 162 </ul>
162 </div> 163 </div>
163 </div> 164 </div>
@@ -165,14 +166,14 @@
165 166
166 <div class="row"> 167 <div class="row">
167 <div class="input-field col s12"> 168 <div class="input-field col s12">
168 {{ form_label(form.rss.rss_limit) }} 169 {{ form_label(form.feed.feed_limit) }}
169 {{ form_errors(form.rss.rss_limit) }} 170 {{ form_errors(form.feed.feed_limit) }}
170 {{ form_widget(form.rss.rss_limit) }} 171 {{ form_widget(form.feed.feed_limit) }}
171 </div> 172 </div>
172 </div> 173 </div>
173 174
174 {{ form_widget(form.rss.save, {'attr': {'class': 'btn waves-effect waves-light'}}) }} 175 {{ form_widget(form.feed.save, {'attr': {'class': 'btn waves-effect waves-light'}}) }}
175 {{ form_rest(form.rss) }} 176 {{ form_rest(form.feed) }}
176 </form> 177 </form>
177 </div> 178 </div>
178 179
@@ -181,6 +182,15 @@
181 {{ form_errors(form.user) }} 182 {{ form_errors(form.user) }}
182 183
183 <div class="row"> 184 <div class="row">
185 <div class="col s12">
186 <h6 class="grey-text">{{ 'config.form_user.login_label'|trans }}</h6>
187 <div>
188 {{ app.user.username }}
189 </div>
190 </div>
191 </div>
192
193 <div class="row">
184 <div class="input-field col s12"> 194 <div class="input-field col s12">
185 {{ form_label(form.user.name) }} 195 {{ form_label(form.user.name) }}
186 {{ form_errors(form.user.name) }} 196 {{ form_errors(form.user.name) }}
@@ -196,59 +206,42 @@
196 </div> 206 </div>
197 </div> 207 </div>
198 208
199 {% if twofactor_auth %} 209 {{ 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 210
206 {{ form_widget(form.user.twoFactorAuthentication) }} 211 {% if twofactor_auth %}
207 {{ form_label(form.user.twoFactorAuthentication) }} 212 <br/>
208 {{ form_errors(form.user.twoFactorAuthentication) }} 213 <br/>
209 </div> 214 <div class="row">
210 <div class="input-field col s1"> 215 <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 }}"> 216
212 <i class="material-icons">live_help</i> 217 <p>{{ 'config.form_user.two_factor_description'|trans }}</p>
213 </a> 218
219 <table>
220 <thead>
221 <tr>
222 <th>{{ 'config.form_user.two_factor.table_method'|trans }}</th>
223 <th>{{ 'config.form_user.two_factor.table_state'|trans }}</th>
224 <th>{{ 'config.form_user.two_factor.table_action'|trans }}</th>
225 </tr>
226 </thead>
227
228 <tbody>
229 <tr>
230 <td>{{ 'config.form_user.two_factor.emailTwoFactor_label'|trans }}</td>
231 <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>
232 <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>
233 </tr>
234 <tr>
235 <td>{{ 'config.form_user.two_factor.googleTwoFactor_label'|trans }}</td>
236 <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>
237 <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>
238 </tr>
239 </tbody>
240 </table>
214 </div> 241 </div>
215 </div>
216 {% endif %} 242 {% endif %}
217
218 {{ form_widget(form.user.save, {'attr': {'class': 'btn waves-effect waves-light'}}) }}
219 {{ form_widget(form.user._token) }} 243 {{ form_widget(form.user._token) }}
220 </form> 244 </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> 245 </div>
253 246
254 <div id="set4" class="col s12"> 247 <div id="set4" class="col s12">
@@ -314,28 +307,77 @@
314 </div> 307 </div>
315 {% endif %} 308 {% endif %}
316 309
317 {{ form_start(form.new_tagging_rule) }} 310 <ul class="row">
318 {{ form_errors(form.new_tagging_rule) }} 311 <li class="col l6 m6 s12">
319 312 <div class="card">
320 <div class="row"> 313 <div class="card-content">
321 <div class="input-field col s12"> 314 <span class="card-title">{{ 'config.form_rules.card.new_tagging_rule'|trans }}</span>
322 {{ form_label(form.new_tagging_rule.rule) }} 315
323 {{ form_errors(form.new_tagging_rule.rule) }} 316 {{ form_start(form.new_tagging_rule) }}
324 {{ form_widget(form.new_tagging_rule.rule) }} 317 {{ form_errors(form.new_tagging_rule) }}
318
319 <div class="row">
320 <div class="input-field col s12">
321 {{ form_label(form.new_tagging_rule.rule) }}
322 {{ form_errors(form.new_tagging_rule.rule) }}
323 {{ form_widget(form.new_tagging_rule.rule) }}
324 </div>
325 </div>
326
327 <div class="row">
328 <div class="input-field col s12">
329 {{ form_label(form.new_tagging_rule.tags) }}
330 {{ form_errors(form.new_tagging_rule.tags) }}
331 {{ form_widget(form.new_tagging_rule.tags) }}
332 </div>
333 </div>
334
335 {{ form_widget(form.new_tagging_rule.save, {'attr': {'class': 'btn waves-effect waves-light'}}) }}
336 {{ form_rest(form.new_tagging_rule) }}
337 </form>
338 </div>
325 </div> 339 </div>
326 </div> 340 </li>
327 341 <li class="col l6 m6 s12">
328 <div class="row"> 342 <div class="card z-depth-1">
329 <div class="input-field col s12"> 343 <div class="card-content">
330 {{ form_label(form.new_tagging_rule.tags) }} 344 <span class="card-title">{{ 'config.form_rules.card.import_tagging_rules'|trans }}</span>
331 {{ form_errors(form.new_tagging_rule.tags) }} 345 <p>{{ 'config.form_rules.card.import_tagging_rules_detail'|trans }}</p>
332 {{ form_widget(form.new_tagging_rule.tags) }} 346 {{ form_start(form.import_tagging_rule) }}
347 {{ form_errors(form.import_tagging_rule) }}
348 <div class="row">
349 <div class="file-field input-field col s12">
350 {{ form_errors(form.import_tagging_rule.file) }}
351 <div class="btn">
352 <span>{{ form.import_tagging_rule.file.vars.label|trans }}</span>
353 {{ form_widget(form.import_tagging_rule.file) }}
354 </div>
355 <div class="file-path-wrapper">
356 <input class="file-path validate" type="text">
357 </div>
358 </div>
359 </div>
360
361 {{ form_widget(form.import_tagging_rule.import, { 'attr': {'class': 'btn waves-effect waves-light'} }) }}
362
363 {{ form_rest(form.import_tagging_rule) }}
364 </form>
365 </div>
333 </div> 366 </div>
334 </div> 367 </li>
335 368 {% if app.user.config.taggingRules is not empty %}
336 {{ form_widget(form.new_tagging_rule.save, {'attr': {'class': 'btn waves-effect waves-light'}}) }} 369 <li class="col l6 m6 s12">
337 {{ form_rest(form.new_tagging_rule) }} 370 <div class="card z-depth-1">
338 </form> 371 <div class="card-content">
372 <span class="card-title">{{ 'config.form_rules.card.export_tagging_rules'|trans }}</span>
373 <p>{{ 'config.form_rules.card.export_tagging_rules_detail'|trans }}</p>
374 <br/>
375 <p><a href="{{ path('export_tagging_rule') }}" class="waves-effect waves-light btn">{{ 'config.form_rules.export'|trans }}</a></p>
376 </div>
377 </div>
378 </li>
379 {% endif %}
380 </ul>
339 381
340 <div class="row"> 382 <div class="row">
341 <div class="input-field col s12"> 383 <div class="input-field col s12">
@@ -422,6 +464,37 @@
422 </div> 464 </div>
423 </div> 465 </div>
424 </div> 466 </div>
467
468 <div id="set6" class="col s12">
469 <div class="row">
470 <h5>{{ 'config.reset.title'|trans }}</h5>
471 <p>{{ 'config.reset.description'|trans }}</p>
472 <a href="{{ path('config_reset', { type: 'annotations'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
473 {{ 'config.reset.annotations'|trans }}
474 </a>
475 <a href="{{ path('config_reset', { type: 'tags'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
476 {{ 'config.reset.tags'|trans }}
477 </a>
478 <a href="{{ path('config_reset', { type: 'archived'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
479 {{ 'config.reset.archived'|trans }}
480 </a>
481 <a href="{{ path('config_reset', { type: 'entries'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
482 {{ 'config.reset.entries'|trans }}
483 </a>
484 </div>
485
486 {% if enabled_users > 1 %}
487 <br /><hr /><br />
488
489 <div class="row">
490 <h5>{{ 'config.form_user.delete.title'|trans }}</h5>
491 <p>{{ 'config.form_user.delete.description'|trans }}</p>
492 <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">
493 {{ 'config.form_user.delete.button'|trans }}
494 </a>
495 </div>
496 {% endif %}
497 </div>
425 </div> 498 </div>
426 499
427 </div> 500 </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..6f405d7f
--- /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">{{ "auth_code"|trans({}, 'SchebTwoFactorBundle') }}</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/Card/_content.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/Card/_content.html.twig
index 1f3cd1a7..1102a0bd 100644
--- a/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/Card/_content.html.twig
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/Card/_content.html.twig
@@ -8,8 +8,11 @@
8 8
9 <div class="{{ subClass|default('original grey-text') }}"> 9 <div class="{{ subClass|default('original grey-text') }}">
10 <a href="{{ entry.url|e }}" target="_blank" title="{{ entry.domainName|removeWww }}" class="tool grey-text">{{ entry.domainName|removeWww }}</a> 10 <a href="{{ entry.url|e }}" target="_blank" title="{{ entry.domainName|removeWww }}" class="tool grey-text">{{ entry.domainName|removeWww }}</a>
11 {% if withTags is defined %} 11 {% if withMetadata is defined %}
12 {% include "@WallabagCore/themes/material/Entry/_tags.html.twig" with {'tags': entry.tags | slice(0, 3), 'entryId': entry.id, 'listClass': ' hide-on-med-and-down'} only %} 12 {% include "@WallabagCore/themes/material/Entry/_tags.html.twig" with {'tags': entry.tags | slice(0, 3), 'entryId': entry.id, 'listClass': ' hide-on-med-and-down'} only %}
13 <div class="reading-time grey-text">
14 <div class="card-reading-time">{% include "@WallabagCore/themes/material/Entry/_reading_time.html.twig" with {'entry': entry} only %}</div>
15 </div>
13 {% endif %} 16 {% endif %}
14 </div> 17 </div>
15</div> 18</div>
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/_card_actions.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/_card_actions.html.twig
index 827f09d9..be764e10 100644
--- a/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/_card_actions.html.twig
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/_card_actions.html.twig
@@ -1,9 +1,11 @@
1<div class="card-action"> 1<div class="card-action">
2 <span class="reading-time grey-text"> 2 <div class="reading-time grey-text">
3 {% include "@WallabagCore/themes/material/Entry/_reading_time.html.twig" with {'entry': entry} only %} 3 <div class="card-reading-time">{% include "@WallabagCore/themes/material/Entry/_reading_time.html.twig" with {'entry': entry} only %}</div>
4 <i class="material-icons hide-on-med-and-down" title="{{ 'entry.view.created_at'|trans }}">today</i> 4 <div class="card-created-at">
5 <span class="hide-on-med-and-down">&nbsp;{{ entry.createdAt|date('Y-m-d') }}</span> 5 <i class="material-icons" title="{{ 'entry.view.created_at'|trans }}">today</i>
6 </span> 6 <span>&nbsp;{{ entry.createdAt|date('Y-m-d') }}</span>
7 </div>
8 </div>
7 9
8 <ul class="tools right"> 10 <ul class="tools right">
9 <li> 11 <li>
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/_card_list.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/_card_list.html.twig
index 1c00f2fa..6a095035 100644
--- a/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/_card_list.html.twig
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/_card_list.html.twig
@@ -5,7 +5,7 @@
5 <span class="preview{{ previewClassModifier }}" style="background-image: url({{ entry.previewPicture | default(asset('wallassets/themes/_global/img/logo-square.svg')) }})"></span> 5 <span class="preview{{ previewClassModifier }}" style="background-image: url({{ entry.previewPicture | default(asset('wallassets/themes/_global/img/logo-square.svg')) }})"></span>
6 </a> 6 </a>
7 </div> 7 </div>
8 {% include "@WallabagCore/themes/material/Entry/Card/_content.html.twig" with {'entry': entry, 'withTags': true, 'subClass': 'metadata'} only %} 8 {% include "@WallabagCore/themes/material/Entry/Card/_content.html.twig" with {'entry': entry, 'withMetadata': true, 'subClass': 'metadata'} only %}
9 <ul class="tools-list hide-on-small-only"> 9 <ul class="tools-list hide-on-small-only">
10 <li> 10 <li>
11 <a title="{{ 'entry.list.toogle_as_read'|trans }}" class="tool grey-text" href="{{ path('archive_entry', { 'id': entry.id }) }}"><i class="material-icons">{% if entry.isArchived == 0 %}done{% else %}unarchive{% endif %}</i></a> 11 <a title="{{ 'entry.list.toogle_as_read'|trans }}" class="tool grey-text" href="{{ path('archive_entry', { 'id': entry.id }) }}"><i class="material-icons">{% if entry.isArchived == 0 %}done{% else %}unarchive{% endif %}</i></a>
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/_reading_time.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/_reading_time.html.twig
index 6ba18768..b7167e95 100644
--- a/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/_reading_time.html.twig
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/_reading_time.html.twig
@@ -1,4 +1,4 @@
1{% set readingTime = entry.readingTime / app.user.config.readingSpeed %} 1{% set readingTime = entry.readingTime / app.user.config.readingSpeed * 200 %}
2<i class="material-icons">timer</i> 2<i class="material-icons">timer</i>
3{% if readingTime > 0 %} 3{% if readingTime > 0 %}
4 <span>{{ 'entry.list.reading_time_minutes_short'|trans({'%readingTime%': readingTime|round}) }}</span> 4 <span>{{ 'entry.list.reading_time_minutes_short'|trans({'%readingTime%': readingTime|round}) }}</span>
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 a137f3c3..3906e1e0 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,16 +63,13 @@
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 %}
69 {% if craue_setting('export_mobi') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'mobi', 'tag' : currentTag }) }}">MOBI</a></li>{% endif %} 69 {% if craue_setting('export_mobi') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'mobi', 'tag' : currentTag }) }}">MOBI</a></li>{% endif %}
70 {% if craue_setting('export_pdf') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'pdf', 'tag' : currentTag }) }}">PDF</a></li>{% endif %} 70 {% if craue_setting('export_pdf') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'pdf', 'tag' : currentTag }) }}">PDF</a></li>{% endif %}
71 {% if craue_setting('export_csv') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'json', 'tag' : currentTag }) }}">JSON</a></li>{% endif %} 71 {% if craue_setting('export_json') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'json', 'tag' : currentTag }) }}">JSON</a></li>{% endif %}
72 {% if craue_setting('export_json') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'csv', 'tag' : currentTag }) }}">CSV</a></li>{% endif %} 72 {% if craue_setting('export_csv') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'csv', 'tag' : currentTag }) }}">CSV</a></li>{% endif %}
73 {% if craue_setting('export_txt') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'txt', 'tag' : currentTag }) }}">TXT</a></li>{% endif %} 73 {% if craue_setting('export_txt') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'txt', 'tag' : currentTag }) }}">TXT</a></li>{% endif %}
74 {% if craue_setting('export_xml') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'xml', 'tag' : currentTag }) }}">XML</a></li>{% endif %} 74 {% if craue_setting('export_xml') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'xml', 'tag' : currentTag }) }}">XML</a></li>{% endif %}
75 </ul> 75 </ul>
@@ -83,6 +83,12 @@
83 <h4 class="center">{{ 'entry.filters.title'|trans }}</h4> 83 <h4 class="center">{{ 'entry.filters.title'|trans }}</h4>
84 84
85 <div class="row"> 85 <div class="row">
86 {% if currentRoute != 'untagged' and nbEntriesUntagged != 0 %}
87 <div class="col s12 center-align">
88 <a href="{{ path('untagged') }}">{{ 'tag.list.see_untagged_entries'|trans }} ({{nbEntriesUntagged}})</a>
89 </div>
90 {% endif %}
91
86 <div class="col s12"> 92 <div class="col s12">
87 <label>{{ 'entry.filters.status_label'|trans }}</label> 93 <label>{{ 'entry.filters.status_label'|trans }}</label>
88 </div> 94 </div>
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/entry.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/entry.html.twig
index c6c19de6..e23fa0e1 100644
--- a/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/entry.html.twig
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/entry.html.twig
@@ -8,7 +8,7 @@
8 <div class="progress"> 8 <div class="progress">
9 <div class="determinate"></div> 9 <div class="determinate"></div>
10 </div> 10 </div>
11 <nav class="hide-on-large-only"> 11 <nav class="hide-on-large-only js-entry-nav-top">
12 <div class="nav-panel-item cyan darken-1"> 12 <div class="nav-panel-item cyan darken-1">
13 <ul> 13 <ul>
14 <li> 14 <li>
@@ -127,7 +127,7 @@
127 {% endif %} 127 {% endif %}
128 {% if craue_setting('share_twitter') %} 128 {% if craue_setting('share_twitter') %}
129 <li> 129 <li>
130 <a href="https://twitter.com/home?status={{entry.title|striptags|url_encode}}%20{{ entry.url|url_encode }}%20via%20@wallabagapp" target="_blank" rel="noopener" class="tool icon-twitter" title="twitter"> 130 <a href="https://twitter.com/share?text={{entry.title|striptags|url_encode}}%20{{ entry.url|url_encode }}%20via%20@wallabagapp" target="_blank" rel="noopener" class="tool icon-twitter" title="twitter">
131 <span>twitter</span> 131 <span>twitter</span>
132 </a> 132 </a>
133 </li> 133 </li>
@@ -209,7 +209,7 @@
209 </li> 209 </li>
210 210
211 <li class="bold"> 211 <li class="bold">
212 <a class="waves-effect collapsible-header" href="mailto:hello@wallabag.org?subject=Wrong%20display%20in%20wallabag&amp;body={{ entry.url|url_encode }}" title="{{ 'entry.view.left_menu.problem.description'|trans }}"> 212 <a class="waves-effect collapsible-header" href="mailto:siteconfig@wallabag.org?subject=Wrong%20display%20in%20wallabag&amp;body={{ entry.url|url_encode }}" title="{{ 'entry.view.left_menu.problem.description'|trans }}">
213 <i class="material-icons small">error</i> 213 <i class="material-icons small">error</i>
214 <span>{{ 'entry.view.left_menu.problem.label'|trans }}</span> 214 <span>{{ 'entry.view.left_menu.problem.label'|trans }}</span>
215 </a> 215 </a>
@@ -223,7 +223,7 @@
223{% block content %} 223{% block content %}
224 <div id="article"> 224 <div id="article">
225 <header class="mbm"> 225 <header class="mbm">
226 <h1>{{ entry.title|striptags|default('entry.default_title'|trans)|raw }} <a href="{{ path('edit', { 'id': entry.id }) }}" title="{{ 'entry.view.edit_title'|trans }}">✎</a></h1> 226 <h1><span{% if entry.language is defined and entry.language is not null %} lang="{{ entry.getHTMLLanguage() }}"{% endif %}>{{ entry.title|striptags|default('entry.default_title'|trans)|raw }}</span> <a href="{{ path('edit', { 'id': entry.id }) }}" title="{{ 'entry.view.edit_title'|trans }}">✎</a></h1>
227 </header> 227 </header>
228 <aside> 228 <aside>
229 <div class="tools"> 229 <div class="tools">
@@ -250,13 +250,13 @@
250 </li> 250 </li>
251 {% endif %} 251 {% endif %}
252 <li> 252 <li>
253 <i class="material-icons link">link</i> 253 <i class="material-icons">link</i>
254 <a href="{{ entry.url|e }}" target="_blank" rel="noopener" title="{{ 'entry.view.original_article'|trans }} : {{ entry.title|striptags }}" class="tool"> 254 <a href="{{ entry.url|e }}" target="_blank" rel="noopener" title="{{ 'entry.view.original_article'|trans }} : {{ entry.title|striptags }}" class="tool">
255 {{ entry.domainName|removeWww }} 255 {{ entry.domainName|removeWww }}
256 </a> 256 </a>
257 </li> 257 </li>
258 <li> 258 <li>
259 <i class="material-icons link">comment</i> 259 <i class="material-icons">comment</i>
260 {{ 'entry.view.annotations_on_the_entry'|transchoice(entry.annotations | length) }} 260 {{ 'entry.view.annotations_on_the_entry'|transchoice(entry.annotations | length) }}
261 </li> 261 </li>
262 {% if entry.originUrl is not empty %} 262 {% if entry.originUrl is not empty %}
@@ -276,12 +276,12 @@
276 </div> 276 </div>
277 277
278 </aside> 278 </aside>
279 <article> 279 <article{% if entry.language is defined and entry.language is not null %} lang="{{ entry.getHTMLLanguage() }}"{% endif %}>
280 {{ entry.content | raw }} 280 {{ entry.content | raw }}
281 </article> 281 </article>
282 282
283 <div class="fixed-action-btn horizontal click-to-toggle hide-on-large-only"> 283 <div class="fixed-action-btn js-fixed-action-btn horizontal click-to-toggle hide-on-large-only">
284 <a class="btn-floating btn-large"> 284 <a class="btn-floating btn-large" data-toggle="actions">
285 <i class="material-icons">menu</i> 285 <i class="material-icons">menu</i>
286 </a> 286 </a>
287 <ul> 287 <ul>
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/new_form.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/new_form.html.twig
index e0d5e794..4cf81167 100644
--- a/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/new_form.html.twig
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/new_form.html.twig
@@ -2,14 +2,14 @@
2 {% if form_errors(form) %} 2 {% if form_errors(form) %}
3 <span class="black-text">{{ form_errors(form) }}</span> 3 <span class="black-text">{{ form_errors(form) }}</span>
4 {% endif %} 4 {% endif %}
5 <button type="submit" class="nav-form-button"><i class="material-icons add">add</i></button> 5 <button type="submit" class="nav-form-button" aria-label="add"><i class="material-icons add" aria-hidden="true"></i></button>
6 6
7 {% if form_errors(form.url) %} 7 {% if form_errors(form.url) %}
8 <span class="black-text">{{ form_errors(form.url) }}</span> 8 <span class="black-text">{{ form_errors(form.url) }}</span>
9 {% endif %} 9 {% endif %}
10 10
11 {{ form_widget(form.url, { 'attr': {'autocomplete': 'off', 'placeholder': 'entry.new.placeholder'} }) }} 11 {{ form_widget(form.url, { 'attr': {'autocomplete': 'off', 'placeholder': 'entry.new.placeholder'} }) }}
12 <i class="material-icons close">clear</i> 12 <i class="material-icons close" aria-label="clear" role="button"></i>
13 13
14 {{ form_rest(form) }} 14 {{ form_rest(form) }}
15</form> 15</form>
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/search_form.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/search_form.html.twig
index ba1b3aac..0ae8b0b3 100644
--- a/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/search_form.html.twig
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/search_form.html.twig
@@ -2,7 +2,7 @@
2 {% if form_errors(form) %} 2 {% if form_errors(form) %}
3 <span class="black-text">{{ form_errors(form) }}</span> 3 <span class="black-text">{{ form_errors(form) }}</span>
4 {% endif %} 4 {% endif %}
5 <button type="submit" class="nav-form-button"><i class="material-icons search">search</i></button> 5 <button type="submit" class="nav-form-button" aria-label="search"><i class="material-icons search" aria-hidden="true"></i></button>
6 6
7 {% if form_errors(form.term) %} 7 {% if form_errors(form.term) %}
8 <span class="black-text">{{ form_errors(form.term) }}</span> 8 <span class="black-text">{{ form_errors(form.term) }}</span>
@@ -11,7 +11,7 @@
11 <input type="hidden" name="currentRoute" value="{{ currentRoute }}" /> 11 <input type="hidden" name="currentRoute" value="{{ currentRoute }}" />
12 12
13 {{ form_widget(form.term, { 'attr': {'autocomplete': 'off', 'placeholder': 'entry.search.placeholder'} }) }} 13 {{ form_widget(form.term, { 'attr': {'autocomplete': 'off', 'placeholder': 'entry.search.placeholder'} }) }}
14 <i class="material-icons close">clear</i> 14 <i class="material-icons close" aria-label="clear" role="button"></i>
15 15
16 {{ form_rest(form) }} 16 {{ form_rest(form) }}
17</form> 17</form>
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..0a3475ef 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 %}
@@ -23,6 +34,10 @@
23 </div> 34 </div>
24 35
25 <div> 36 <div>
26 <a href="{{ path('untagged') }}">{{ 'tag.list.see_untagged_entries'|trans }}</a> 37 {% if nbEntriesUntagged == 0 %}
38 {{ 'tag.list.no_untagged_entries'|trans }}
39 {% else %}
40 <a href="{{ path('untagged') }}">{{ 'tag.list.see_untagged_entries'|trans }} ({{nbEntriesUntagged}})</a>
41 {% endif %}
27 </div> 42 </div>
28{% endblock %} 43{% endblock %}
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..c51d07fc 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 %}">
@@ -89,6 +91,9 @@
89 <li class="bold {% if currentRoute == 'howto' %}active{% endif %}"> 91 <li class="bold {% if currentRoute == 'howto' %}active{% endif %}">
90 <a class="waves-effect" href="{{ path('howto') }}">{{ 'menu.left.howto'|trans }}</a> 92 <a class="waves-effect" href="{{ path('howto') }}">{{ 'menu.left.howto'|trans }}</a>
91 </li> 93 </li>
94 <li class="bold {% if currentRoute == 'quickstart' %}active{% endif %}">
95 <a class="waves-effect" href="{{ path('quickstart') }}">{{ 'menu.left.quickstart'|trans }}</a>
96 </li>
92 <li class="bold"> 97 <li class="bold">
93 <a class="waves-effect icon icon-power" href="{{ path('fos_user_security_logout') }}">{{ 'menu.left.logout'|trans }}</a> 98 <a class="waves-effect icon icon-power" href="{{ path('fos_user_security_logout') }}">{{ 'menu.left.logout'|trans }}</a>
94 </li> 99 </li>
@@ -113,6 +118,13 @@
113 <i class="material-icons">search</i> 118 <i class="material-icons">search</i>
114 </a> 119 </a>
115 </li> 120 </li>
121 {% if activeRoute %}
122 <li id="button_random">
123 <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 }) }}">
124 <i class="material-icons">casino</i>
125 </a>
126 </li>
127 {% endif %}
116 <li id="button_filters"> 128 <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"> 129 <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> 130 <i class="material-icons">filter_list</i>
@@ -125,7 +137,7 @@
125 </li> 137 </li>
126 </ul> 138 </ul>
127 </div> 139 </div>
128 {{ render(controller("WallabagCoreBundle:Entry:searchForm", {'currentRoute': app.request.attributes.get('_route')})) }} 140 {{ render(controller("WallabagCoreBundle:Entry:searchForm", {'currentRoute': currentRoute})) }}
129 {{ render(controller("WallabagCoreBundle:Entry:addEntryForm")) }} 141 {{ render(controller("WallabagCoreBundle:Entry:addEntryForm")) }}
130 </div> 142 </div>
131 </nav> 143 </nav>
diff --git a/src/Wallabag/CoreBundle/Tools/Utils.php b/src/Wallabag/CoreBundle/Tools/Utils.php
index 46bb1dc5..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 *
@@ -20,15 +20,14 @@ class Utils
20 } 20 }
21 21
22 /** 22 /**
23 * For a given text, we calculate reading time for an article 23 * For a given text, we calculate reading time for an article based on 200 words per minute.
24 * based on 200 words per minute.
25 * 24 *
26 * @param $text 25 * @param string $text
27 * 26 *
28 * @return float 27 * @return float
29 */ 28 */
30 public static function getReadingTime($text) 29 public static function getReadingTime($text)
31 { 30 {
32 return floor(\count(preg_split('~[^\p{L}\p{N}\']+~u', strip_tags($text))) / 200); 31 return floor(\count(preg_split('~([^\p{L}\p{N}\']+|(\p{Han}|\p{Hiragana}|\p{Katakana}|\p{Hangul}){1,2})~u', strip_tags($text))) / 200);
33 } 32 }
34} 33}
diff --git a/src/Wallabag/CoreBundle/Twig/WallabagExtension.php b/src/Wallabag/CoreBundle/Twig/WallabagExtension.php
index 00b1e595..02f17f50 100644
--- a/src/Wallabag/CoreBundle/Twig/WallabagExtension.php
+++ b/src/Wallabag/CoreBundle/Twig/WallabagExtension.php
@@ -4,10 +4,14 @@ namespace Wallabag\CoreBundle\Twig;
4 4
5use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; 5use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
6use Symfony\Component\Translation\TranslatorInterface; 6use Symfony\Component\Translation\TranslatorInterface;
7use Twig\Extension\AbstractExtension;
8use Twig\Extension\GlobalsInterface;
9use Twig\TwigFilter;
10use Twig\TwigFunction;
7use Wallabag\CoreBundle\Repository\EntryRepository; 11use Wallabag\CoreBundle\Repository\EntryRepository;
8use Wallabag\CoreBundle\Repository\TagRepository; 12use Wallabag\CoreBundle\Repository\TagRepository;
9 13
10class WallabagExtension extends \Twig_Extension implements \Twig_Extension_GlobalsInterface 14class WallabagExtension extends AbstractExtension implements GlobalsInterface
11{ 15{
12 private $tokenStorage; 16 private $tokenStorage;
13 private $entryRepository; 17 private $entryRepository;
@@ -24,20 +28,26 @@ class WallabagExtension extends \Twig_Extension implements \Twig_Extension_Globa
24 $this->translator = $translator; 28 $this->translator = $translator;
25 } 29 }
26 30
31 public function getGlobals()
32 {
33 return [];
34 }
35
27 public function getFilters() 36 public function getFilters()
28 { 37 {
29 return [ 38 return [
30 new \Twig_SimpleFilter('removeWww', [$this, 'removeWww']), 39 new TwigFilter('removeWww', [$this, 'removeWww']),
31 new \Twig_SimpleFilter('removeSchemeAndWww', [$this, 'removeSchemeAndWww']), 40 new TwigFilter('removeScheme', [$this, 'removeScheme']),
41 new TwigFilter('removeSchemeAndWww', [$this, 'removeSchemeAndWww']),
32 ]; 42 ];
33 } 43 }
34 44
35 public function getFunctions() 45 public function getFunctions()
36 { 46 {
37 return [ 47 return [
38 new \Twig_SimpleFunction('count_entries', [$this, 'countEntries']), 48 new TwigFunction('count_entries', [$this, 'countEntries']),
39 new \Twig_SimpleFunction('count_tags', [$this, 'countTags']), 49 new TwigFunction('count_tags', [$this, 'countTags']),
40 new \Twig_SimpleFunction('display_stats', [$this, 'displayStats']), 50 new TwigFunction('display_stats', [$this, 'displayStats']),
41 ]; 51 ];
42 } 52 }
43 53
@@ -46,11 +56,14 @@ class WallabagExtension extends \Twig_Extension implements \Twig_Extension_Globa
46 return preg_replace('/^www\./i', '', $url); 56 return preg_replace('/^www\./i', '', $url);
47 } 57 }
48 58
59 public function removeScheme($url)
60 {
61 return preg_replace('#^https?://#i', '', $url);
62 }
63
49 public function removeSchemeAndWww($url) 64 public function removeSchemeAndWww($url)
50 { 65 {
51 return $this->removeWww( 66 return $this->removeWww($this->removeScheme($url));
52 preg_replace('@^https?://@i', '', $url)
53 );
54 } 67 }
55 68
56 /** 69 /**
diff --git a/src/Wallabag/ImportBundle/Consumer/AbstractConsumer.php b/src/Wallabag/ImportBundle/Consumer/AbstractConsumer.php
index b035f5cc..e4bfbdf0 100644
--- a/src/Wallabag/ImportBundle/Consumer/AbstractConsumer.php
+++ b/src/Wallabag/ImportBundle/Consumer/AbstractConsumer.php
@@ -52,6 +52,13 @@ abstract class AbstractConsumer
52 52
53 $this->import->setUser($user); 53 $this->import->setUser($user);
54 54
55 if (false === $this->import->validateEntry($storedEntry)) {
56 $this->logger->warning('Entry is invalid', ['entry' => $storedEntry]);
57
58 // return true to skip message
59 return true;
60 }
61
55 $entry = $this->import->parseEntry($storedEntry); 62 $entry = $this->import->parseEntry($storedEntry);
56 63
57 if (null === $entry) { 64 if (null === $entry) {
diff --git a/src/Wallabag/ImportBundle/Controller/BrowserController.php b/src/Wallabag/ImportBundle/Controller/BrowserController.php
index 6418925c..8c2bdfe5 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
@@ -13,8 +13,6 @@ abstract class BrowserController extends Controller
13 /** 13 /**
14 * @Route("/browser", name="import_browser") 14 * @Route("/browser", name="import_browser")
15 * 15 *
16 * @param Request $request
17 *
18 * @return Response 16 * @return Response
19 */ 17 */
20 public function indexAction(Request $request) 18 public function indexAction(Request $request)
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/ElcuratorController.php b/src/Wallabag/ImportBundle/Controller/ElcuratorController.php
new file mode 100644
index 00000000..174c2c96
--- /dev/null
+++ b/src/Wallabag/ImportBundle/Controller/ElcuratorController.php
@@ -0,0 +1,41 @@
1<?php
2
3namespace Wallabag\ImportBundle\Controller;
4
5use Symfony\Component\HttpFoundation\Request;
6use Symfony\Component\Routing\Annotation\Route;
7
8class ElcuratorController extends WallabagController
9{
10 /**
11 * @Route("/elcurator", name="import_elcurator")
12 */
13 public function indexAction(Request $request)
14 {
15 return parent::indexAction($request);
16 }
17
18 /**
19 * {@inheritdoc}
20 */
21 protected function getImportService()
22 {
23 $service = $this->get('wallabag_import.elcurator.import');
24
25 if ($this->get('craue_config')->get('import_with_rabbitmq')) {
26 $service->setProducer($this->get('old_sound_rabbit_mq.import_elcurator_producer'));
27 } elseif ($this->get('craue_config')->get('import_with_redis')) {
28 $service->setProducer($this->get('wallabag_import.producer.redis.elcurator'));
29 }
30
31 return $service;
32 }
33
34 /**
35 * {@inheritdoc}
36 */
37 protected function getImportTemplate()
38 {
39 return 'WallabagImportBundle:Elcurator:index.html.twig';
40 }
41}
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..5a7e53d6 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{
@@ -43,6 +43,7 @@ class ImportController extends Controller
43 + $this->getTotalMessageInRabbitQueue('chrome') 43 + $this->getTotalMessageInRabbitQueue('chrome')
44 + $this->getTotalMessageInRabbitQueue('instapaper') 44 + $this->getTotalMessageInRabbitQueue('instapaper')
45 + $this->getTotalMessageInRabbitQueue('pinboard') 45 + $this->getTotalMessageInRabbitQueue('pinboard')
46 + $this->getTotalMessageInRabbitQueue('elcurator')
46 ; 47 ;
47 } catch (\Exception $e) { 48 } catch (\Exception $e) {
48 $rabbitNotInstalled = true; 49 $rabbitNotInstalled = true;
@@ -59,6 +60,7 @@ class ImportController extends Controller
59 + $redis->llen('wallabag.import.chrome') 60 + $redis->llen('wallabag.import.chrome')
60 + $redis->llen('wallabag.import.instapaper') 61 + $redis->llen('wallabag.import.instapaper')
61 + $redis->llen('wallabag.import.pinboard') 62 + $redis->llen('wallabag.import.pinboard')
63 + $redis->llen('wallabag.import.elcurator')
62 ; 64 ;
63 } catch (\Exception $e) { 65 } catch (\Exception $e) {
64 $redisNotInstalled = true; 66 $redisNotInstalled = true;
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/WallabagController.php b/src/Wallabag/ImportBundle/Controller/WallabagController.php
index d182dd2c..5180006d 100644
--- a/src/Wallabag/ImportBundle/Controller/WallabagController.php
+++ b/src/Wallabag/ImportBundle/Controller/WallabagController.php
@@ -16,8 +16,6 @@ abstract class WallabagController extends Controller
16 /** 16 /**
17 * Handle import request. 17 * Handle import request.
18 * 18 *
19 * @param Request $request
20 *
21 * @return Response|RedirectResponse 19 * @return Response|RedirectResponse
22 */ 20 */
23 public function indexAction(Request $request) 21 public function indexAction(Request $request)
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/AbstractImport.php b/src/Wallabag/ImportBundle/Import/AbstractImport.php
index 58a234f4..1b073e99 100644
--- a/src/Wallabag/ImportBundle/Import/AbstractImport.php
+++ b/src/Wallabag/ImportBundle/Import/AbstractImport.php
@@ -46,8 +46,6 @@ abstract class AbstractImport implements ImportInterface
46 /** 46 /**
47 * Set RabbitMQ/Redis Producer to send each entry to a queue. 47 * Set RabbitMQ/Redis Producer to send each entry to a queue.
48 * This method should be called when user has enabled RabbitMQ. 48 * This method should be called when user has enabled RabbitMQ.
49 *
50 * @param ProducerInterface $producer
51 */ 49 */
52 public function setProducer(ProducerInterface $producer) 50 public function setProducer(ProducerInterface $producer)
53 { 51 {
@@ -57,8 +55,6 @@ abstract class AbstractImport implements ImportInterface
57 /** 55 /**
58 * Set current user. 56 * Set current user.
59 * Could the current *connected* user or one retrieve by the consumer. 57 * Could the current *connected* user or one retrieve by the consumer.
60 *
61 * @param User $user
62 */ 58 */
63 public function setUser(User $user) 59 public function setUser(User $user)
64 { 60 {
@@ -112,13 +108,18 @@ abstract class AbstractImport implements ImportInterface
112 /** 108 /**
113 * Parse one entry. 109 * Parse one entry.
114 * 110 *
115 * @param array $importedEntry
116 *
117 * @return Entry 111 * @return Entry
118 */ 112 */
119 abstract public function parseEntry(array $importedEntry); 113 abstract public function parseEntry(array $importedEntry);
120 114
121 /** 115 /**
116 * Validate that an entry is valid (like has some required keys, etc.).
117 *
118 * @return bool
119 */
120 abstract public function validateEntry(array $importedEntry);
121
122 /**
122 * Fetch content from the ContentProxy (using graby). 123 * Fetch content from the ContentProxy (using graby).
123 * If it fails return the given entry to be saved in all case (to avoid user to loose the content). 124 * If it fails return the given entry to be saved in all case (to avoid user to loose the content).
124 * 125 *
@@ -140,10 +141,8 @@ abstract class AbstractImport implements ImportInterface
140 141
141 /** 142 /**
142 * Parse and insert all given entries. 143 * Parse and insert all given entries.
143 *
144 * @param $entries
145 */ 144 */
146 protected function parseEntries($entries) 145 protected function parseEntries(array $entries)
147 { 146 {
148 $i = 1; 147 $i = 1;
149 $entryToBeFlushed = []; 148 $entryToBeFlushed = [];
@@ -153,6 +152,10 @@ abstract class AbstractImport implements ImportInterface
153 $importedEntry = $this->setEntryAsRead($importedEntry); 152 $importedEntry = $this->setEntryAsRead($importedEntry);
154 } 153 }
155 154
155 if (false === $this->validateEntry($importedEntry)) {
156 continue;
157 }
158
156 $entry = $this->parseEntry($importedEntry); 159 $entry = $this->parseEntry($importedEntry);
157 160
158 if (null === $entry) { 161 if (null === $entry) {
@@ -197,8 +200,6 @@ abstract class AbstractImport implements ImportInterface
197 * 200 *
198 * Faster parse entries for Producer. 201 * Faster parse entries for Producer.
199 * We don't care to make check at this time. They'll be done by the consumer. 202 * We don't care to make check at this time. They'll be done by the consumer.
200 *
201 * @param array $entries
202 */ 203 */
203 protected function parseEntriesForProducer(array $entries) 204 protected function parseEntriesForProducer(array $entries)
204 { 205 {
@@ -220,8 +221,6 @@ abstract class AbstractImport implements ImportInterface
220 * Set current imported entry to archived / read. 221 * Set current imported entry to archived / read.
221 * Implementation is different accross all imports. 222 * Implementation is different accross all imports.
222 * 223 *
223 * @param array $importedEntry
224 *
225 * @return array 224 * @return array
226 */ 225 */
227 abstract protected function setEntryAsRead(array $importedEntry); 226 abstract protected function setEntryAsRead(array $importedEntry);
diff --git a/src/Wallabag/ImportBundle/Import/BrowserImport.php b/src/Wallabag/ImportBundle/Import/BrowserImport.php
index 225f1791..ea7afd3d 100644
--- a/src/Wallabag/ImportBundle/Import/BrowserImport.php
+++ b/src/Wallabag/ImportBundle/Import/BrowserImport.php
@@ -77,7 +77,7 @@ abstract class BrowserImport extends AbstractImport
77 */ 77 */
78 public function parseEntry(array $importedEntry) 78 public function parseEntry(array $importedEntry)
79 { 79 {
80 if ((!array_key_exists('guid', $importedEntry) || (!array_key_exists('id', $importedEntry))) && \is_array(reset($importedEntry))) { 80 if ((!\array_key_exists('guid', $importedEntry) || (!\array_key_exists('id', $importedEntry))) && \is_array(reset($importedEntry))) {
81 if ($this->producer) { 81 if ($this->producer) {
82 $this->parseEntriesForProducer($importedEntry); 82 $this->parseEntriesForProducer($importedEntry);
83 83
@@ -89,7 +89,7 @@ abstract class BrowserImport extends AbstractImport
89 return; 89 return;
90 } 90 }
91 91
92 if (array_key_exists('children', $importedEntry)) { 92 if (\array_key_exists('children', $importedEntry)) {
93 if ($this->producer) { 93 if ($this->producer) {
94 $this->parseEntriesForProducer($importedEntry['children']); 94 $this->parseEntriesForProducer($importedEntry['children']);
95 95
@@ -101,11 +101,11 @@ abstract class BrowserImport extends AbstractImport
101 return; 101 return;
102 } 102 }
103 103
104 if (!array_key_exists('uri', $importedEntry) && !array_key_exists('url', $importedEntry)) { 104 if (!\array_key_exists('uri', $importedEntry) && !\array_key_exists('url', $importedEntry)) {
105 return; 105 return;
106 } 106 }
107 107
108 $url = array_key_exists('uri', $importedEntry) ? $importedEntry['uri'] : $importedEntry['url']; 108 $url = \array_key_exists('uri', $importedEntry) ? $importedEntry['uri'] : $importedEntry['url'];
109 109
110 $existingEntry = $this->em 110 $existingEntry = $this->em
111 ->getRepository('WallabagCoreBundle:Entry') 111 ->getRepository('WallabagCoreBundle:Entry')
@@ -126,14 +126,14 @@ abstract class BrowserImport extends AbstractImport
126 // update entry with content (in case fetching failed, the given entry will be return) 126 // update entry with content (in case fetching failed, the given entry will be return)
127 $this->fetchContent($entry, $data['url'], $data); 127 $this->fetchContent($entry, $data['url'], $data);
128 128
129 if (array_key_exists('tags', $data)) { 129 if (\array_key_exists('tags', $data)) {
130 $this->tagsAssigner->assignTagsToEntry( 130 $this->tagsAssigner->assignTagsToEntry(
131 $entry, 131 $entry,
132 $data['tags'] 132 $data['tags']
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();
@@ -148,10 +148,8 @@ abstract class BrowserImport extends AbstractImport
148 148
149 /** 149 /**
150 * Parse and insert all given entries. 150 * Parse and insert all given entries.
151 *
152 * @param $entries
153 */ 151 */
154 protected function parseEntries($entries) 152 protected function parseEntries(array $entries)
155 { 153 {
156 $i = 1; 154 $i = 1;
157 $entryToBeFlushed = []; 155 $entryToBeFlushed = [];
@@ -199,8 +197,6 @@ abstract class BrowserImport extends AbstractImport
199 * 197 *
200 * Faster parse entries for Producer. 198 * Faster parse entries for Producer.
201 * We don't care to make check at this time. They'll be done by the consumer. 199 * We don't care to make check at this time. They'll be done by the consumer.
202 *
203 * @param array $entries
204 */ 200 */
205 protected function parseEntriesForProducer(array $entries) 201 protected function parseEntriesForProducer(array $entries)
206 { 202 {
diff --git a/src/Wallabag/ImportBundle/Import/ChromeImport.php b/src/Wallabag/ImportBundle/Import/ChromeImport.php
index 09183abe..4ae82ade 100644
--- a/src/Wallabag/ImportBundle/Import/ChromeImport.php
+++ b/src/Wallabag/ImportBundle/Import/ChromeImport.php
@@ -33,6 +33,18 @@ class ChromeImport extends BrowserImport
33 /** 33 /**
34 * {@inheritdoc} 34 * {@inheritdoc}
35 */ 35 */
36 public function validateEntry(array $importedEntry)
37 {
38 if (empty($importedEntry['url'])) {
39 return false;
40 }
41
42 return true;
43 }
44
45 /**
46 * {@inheritdoc}
47 */
36 protected function prepareEntry(array $entry = []) 48 protected function prepareEntry(array $entry = [])
37 { 49 {
38 $data = [ 50 $data = [
@@ -45,7 +57,7 @@ class ChromeImport extends BrowserImport
45 'created_at' => substr($entry['date_added'], 0, 10), 57 'created_at' => substr($entry['date_added'], 0, 10),
46 ]; 58 ];
47 59
48 if (array_key_exists('tags', $entry) && '' !== $entry['tags']) { 60 if (\array_key_exists('tags', $entry) && '' !== $entry['tags']) {
49 $data['tags'] = $entry['tags']; 61 $data['tags'] = $entry['tags'];
50 } 62 }
51 63
diff --git a/src/Wallabag/ImportBundle/Import/ElcuratorImport.php b/src/Wallabag/ImportBundle/Import/ElcuratorImport.php
new file mode 100644
index 00000000..d1281613
--- /dev/null
+++ b/src/Wallabag/ImportBundle/Import/ElcuratorImport.php
@@ -0,0 +1,54 @@
1<?php
2
3namespace Wallabag\ImportBundle\Import;
4
5class ElcuratorImport extends WallabagImport
6{
7 /**
8 * {@inheritdoc}
9 */
10 public function getName()
11 {
12 return 'elcurator';
13 }
14
15 /**
16 * {@inheritdoc}
17 */
18 public function getUrl()
19 {
20 return 'import_elcurator';
21 }
22
23 /**
24 * {@inheritdoc}
25 */
26 public function getDescription()
27 {
28 return 'import.elcurator.description';
29 }
30
31 /**
32 * {@inheritdoc}
33 */
34 protected function prepareEntry($entry = [])
35 {
36 return [
37 'url' => $entry['url'],
38 'title' => $entry['title'],
39 'created_at' => $entry['created_at'],
40 'is_archived' => 0,
41 'is_starred' => $entry['is_saved'],
42 ] + $entry;
43 }
44
45 /**
46 * {@inheritdoc}
47 */
48 protected function setEntryAsRead(array $importedEntry)
49 {
50 $importedEntry['is_archived'] = 1;
51
52 return $importedEntry;
53 }
54}
diff --git a/src/Wallabag/ImportBundle/Import/FirefoxImport.php b/src/Wallabag/ImportBundle/Import/FirefoxImport.php
index 73269fe1..b3558f21 100644
--- a/src/Wallabag/ImportBundle/Import/FirefoxImport.php
+++ b/src/Wallabag/ImportBundle/Import/FirefoxImport.php
@@ -33,6 +33,18 @@ class FirefoxImport extends BrowserImport
33 /** 33 /**
34 * {@inheritdoc} 34 * {@inheritdoc}
35 */ 35 */
36 public function validateEntry(array $importedEntry)
37 {
38 if (empty($importedEntry['uri'])) {
39 return false;
40 }
41
42 return true;
43 }
44
45 /**
46 * {@inheritdoc}
47 */
36 protected function prepareEntry(array $entry = []) 48 protected function prepareEntry(array $entry = [])
37 { 49 {
38 $data = [ 50 $data = [
@@ -45,7 +57,7 @@ class FirefoxImport extends BrowserImport
45 'created_at' => substr($entry['dateAdded'], 0, 10), 57 'created_at' => substr($entry['dateAdded'], 0, 10),
46 ]; 58 ];
47 59
48 if (array_key_exists('tags', $entry) && '' !== $entry['tags']) { 60 if (\array_key_exists('tags', $entry) && '' !== $entry['tags']) {
49 $data['tags'] = $entry['tags']; 61 $data['tags'] = $entry['tags'];
50 } 62 }
51 63
diff --git a/src/Wallabag/ImportBundle/Import/ImportChain.php b/src/Wallabag/ImportBundle/Import/ImportChain.php
index 9dd77956..e1b5867d 100644
--- a/src/Wallabag/ImportBundle/Import/ImportChain.php
+++ b/src/Wallabag/ImportBundle/Import/ImportChain.php
@@ -14,8 +14,7 @@ class ImportChain
14 /** 14 /**
15 * Add an import to the chain. 15 * Add an import to the chain.
16 * 16 *
17 * @param ImportInterface $import 17 * @param string $alias
18 * @param string $alias
19 */ 18 */
20 public function addImport(ImportInterface $import, $alias) 19 public function addImport(ImportInterface $import, $alias)
21 { 20 {
diff --git a/src/Wallabag/ImportBundle/Import/InstapaperImport.php b/src/Wallabag/ImportBundle/Import/InstapaperImport.php
index e4f0970c..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
@@ -108,6 +111,18 @@ class InstapaperImport extends AbstractImport
108 /** 111 /**
109 * {@inheritdoc} 112 * {@inheritdoc}
110 */ 113 */
114 public function validateEntry(array $importedEntry)
115 {
116 if (empty($importedEntry['url'])) {
117 return false;
118 }
119
120 return true;
121 }
122
123 /**
124 * {@inheritdoc}
125 */
111 public function parseEntry(array $importedEntry) 126 public function parseEntry(array $importedEntry)
112 { 127 {
113 $existingEntry = $this->em 128 $existingEntry = $this->em
@@ -135,7 +150,7 @@ class InstapaperImport extends AbstractImport
135 ); 150 );
136 } 151 }
137 152
138 $entry->setArchived($importedEntry['is_archived']); 153 $entry->updateArchived($importedEntry['is_archived']);
139 $entry->setStarred($importedEntry['is_starred']); 154 $entry->setStarred($importedEntry['is_starred']);
140 155
141 $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 110b0464..202eb1b3 100644
--- a/src/Wallabag/ImportBundle/Import/PinboardImport.php
+++ b/src/Wallabag/ImportBundle/Import/PinboardImport.php
@@ -83,6 +83,18 @@ class PinboardImport extends AbstractImport
83 /** 83 /**
84 * {@inheritdoc} 84 * {@inheritdoc}
85 */ 85 */
86 public function validateEntry(array $importedEntry)
87 {
88 if (empty($importedEntry['href'])) {
89 return false;
90 }
91
92 return true;
93 }
94
95 /**
96 * {@inheritdoc}
97 */
86 public function parseEntry(array $importedEntry) 98 public function parseEntry(array $importedEntry)
87 { 99 {
88 $existingEntry = $this->em 100 $existingEntry = $this->em
@@ -119,7 +131,7 @@ class PinboardImport extends AbstractImport
119 ); 131 );
120 } 132 }
121 133
122 $entry->setArchived($data['is_archived']); 134 $entry->updateArchived($data['is_archived']);
123 $entry->setStarred($data['is_starred']); 135 $entry->setStarred($data['is_starred']);
124 $entry->setCreatedAt(new \DateTime($data['created_at'])); 136 $entry->setCreatedAt(new \DateTime($data['created_at']));
125 137
diff --git a/src/Wallabag/ImportBundle/Import/PocketImport.php b/src/Wallabag/ImportBundle/Import/PocketImport.php
index c1b35b7e..24fdaa2b 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,23 @@ 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 public function setClient(HttpClient $client, MessageFactory $messageFactory = null)
156 {
157 $this->client = new HttpMethodsClient(new PluginClient($client, [new ErrorPlugin()]), $messageFactory ?: MessageFactoryDiscovery::find());
158 }
159
160 /**
161 * {@inheritdoc}
165 */ 162 */
166 public function setClient(Client $client) 163 public function validateEntry(array $importedEntry)
167 { 164 {
168 $this->client = $client; 165 if (empty($importedEntry['resolved_url']) && empty($importedEntry['given_url'])) {
166 return false;
167 }
168
169 return true;
169 } 170 }
170 171
171 /** 172 /**
@@ -194,10 +195,10 @@ class PocketImport extends AbstractImport
194 $this->fetchContent($entry, $url); 195 $this->fetchContent($entry, $url);
195 196
196 // 0, 1, 2 - 1 if the item is archived - 2 if the item should be deleted 197 // 0, 1, 2 - 1 if the item is archived - 2 if the item should be deleted
197 $entry->setArchived(1 === $importedEntry['status'] || $this->markAsRead); 198 $entry->updateArchived(1 === (int) $importedEntry['status'] || $this->markAsRead);
198 199
199 // 0 or 1 - 1 If the item is starred 200 // 0 or 1 - 1 if the item is starred
200 $entry->setStarred(1 === $importedEntry['favorite']); 201 $entry->setStarred(1 === (int) $importedEntry['favorite']);
201 202
202 $title = 'Untitled'; 203 $title = 'Untitled';
203 if (isset($importedEntry['resolved_title']) && '' !== $importedEntry['resolved_title']) { 204 if (isset($importedEntry['resolved_title']) && '' !== $importedEntry['resolved_title']) {
@@ -240,4 +241,15 @@ class PocketImport extends AbstractImport
240 241
241 return $importedEntry; 242 return $importedEntry;
242 } 243 }
244
245 protected function jsonDecode(ResponseInterface $response)
246 {
247 $data = json_decode((string) $response->getBody(), true);
248
249 if (JSON_ERROR_NONE !== json_last_error()) {
250 throw new \InvalidArgumentException('Unable to parse JSON data: ' . json_last_error_msg());
251 }
252
253 return $data;
254 }
243} 255}
diff --git a/src/Wallabag/ImportBundle/Import/ReadabilityImport.php b/src/Wallabag/ImportBundle/Import/ReadabilityImport.php
index 002b27f4..c5abf189 100644
--- a/src/Wallabag/ImportBundle/Import/ReadabilityImport.php
+++ b/src/Wallabag/ImportBundle/Import/ReadabilityImport.php
@@ -83,6 +83,18 @@ class ReadabilityImport extends AbstractImport
83 /** 83 /**
84 * {@inheritdoc} 84 * {@inheritdoc}
85 */ 85 */
86 public function validateEntry(array $importedEntry)
87 {
88 if (empty($importedEntry['article__url'])) {
89 return false;
90 }
91
92 return true;
93 }
94
95 /**
96 * {@inheritdoc}
97 */
86 public function parseEntry(array $importedEntry) 98 public function parseEntry(array $importedEntry)
87 { 99 {
88 $existingEntry = $this->em 100 $existingEntry = $this->em
@@ -111,7 +123,7 @@ class ReadabilityImport extends AbstractImport
111 // 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)
112 $this->fetchContent($entry, $data['url'], $data); 124 $this->fetchContent($entry, $data['url'], $data);
113 125
114 $entry->setArchived($data['is_archived']); 126 $entry->updateArchived($data['is_archived']);
115 $entry->setStarred($data['is_starred']); 127 $entry->setStarred($data['is_starred']);
116 $entry->setCreatedAt(new \DateTime($data['created_at'])); 128 $entry->setCreatedAt(new \DateTime($data['created_at']));
117 129
diff --git a/src/Wallabag/ImportBundle/Import/WallabagImport.php b/src/Wallabag/ImportBundle/Import/WallabagImport.php
index c64ccd64..75a28fbf 100644
--- a/src/Wallabag/ImportBundle/Import/WallabagImport.php
+++ b/src/Wallabag/ImportBundle/Import/WallabagImport.php
@@ -89,6 +89,18 @@ abstract class WallabagImport extends AbstractImport
89 /** 89 /**
90 * {@inheritdoc} 90 * {@inheritdoc}
91 */ 91 */
92 public function validateEntry(array $importedEntry)
93 {
94 if (empty($importedEntry['url'])) {
95 return false;
96 }
97
98 return true;
99 }
100
101 /**
102 * {@inheritdoc}
103 */
92 public function parseEntry(array $importedEntry) 104 public function parseEntry(array $importedEntry)
93 { 105 {
94 $existingEntry = $this->em 106 $existingEntry = $this->em
@@ -110,7 +122,7 @@ abstract class WallabagImport extends AbstractImport
110 // update entry with content (in case fetching failed, the given entry will be return) 122 // update entry with content (in case fetching failed, the given entry will be return)
111 $this->fetchContent($entry, $data['url'], $data); 123 $this->fetchContent($entry, $data['url'], $data);
112 124
113 if (array_key_exists('tags', $data)) { 125 if (\array_key_exists('tags', $data)) {
114 $this->tagsAssigner->assignTagsToEntry( 126 $this->tagsAssigner->assignTagsToEntry(
115 $entry, 127 $entry,
116 $data['tags'], 128 $data['tags'],
@@ -122,7 +134,7 @@ abstract class WallabagImport extends AbstractImport
122 $entry->setPreviewPicture($importedEntry['preview_picture']); 134 $entry->setPreviewPicture($importedEntry['preview_picture']);
123 } 135 }
124 136
125 $entry->setArchived($data['is_archived']); 137 $entry->updateArchived($data['is_archived']);
126 $entry->setStarred($data['is_starred']); 138 $entry->setStarred($data['is_starred']);
127 139
128 if (!empty($data['created_at'])) { 140 if (!empty($data['created_at'])) {
diff --git a/src/Wallabag/ImportBundle/Import/WallabagV1Import.php b/src/Wallabag/ImportBundle/Import/WallabagV1Import.php
index b9bb525a..e0562611 100644
--- a/src/Wallabag/ImportBundle/Import/WallabagV1Import.php
+++ b/src/Wallabag/ImportBundle/Import/WallabagV1Import.php
@@ -61,7 +61,7 @@ class WallabagV1Import extends WallabagImport
61 $data['html'] = $this->fetchingErrorMessage; 61 $data['html'] = $this->fetchingErrorMessage;
62 } 62 }
63 63
64 if (array_key_exists('tags', $entry) && '' !== $entry['tags']) { 64 if (\array_key_exists('tags', $entry) && '' !== $entry['tags']) {
65 $data['tags'] = $entry['tags']; 65 $data['tags'] = $entry['tags'];
66 } 66 }
67 67
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/rabbit.yml b/src/Wallabag/ImportBundle/Resources/config/rabbit.yml
index e9ecb846..0bf0e761 100644
--- a/src/Wallabag/ImportBundle/Resources/config/rabbit.yml
+++ b/src/Wallabag/ImportBundle/Resources/config/rabbit.yml
@@ -48,6 +48,14 @@ services:
48 - "@wallabag_import.wallabag_v2.import" 48 - "@wallabag_import.wallabag_v2.import"
49 - "@event_dispatcher" 49 - "@event_dispatcher"
50 - "@logger" 50 - "@logger"
51 wallabag_import.consumer.amqp.elcurator:
52 class: Wallabag\ImportBundle\Consumer\AMQPEntryConsumer
53 arguments:
54 - "@doctrine.orm.entity_manager"
55 - "@wallabag_user.user_repository"
56 - "@wallabag_import.elcurator.import"
57 - "@event_dispatcher"
58 - "@logger"
51 wallabag_import.consumer.amqp.firefox: 59 wallabag_import.consumer.amqp.firefox:
52 class: Wallabag\ImportBundle\Consumer\AMQPEntryConsumer 60 class: Wallabag\ImportBundle\Consumer\AMQPEntryConsumer
53 arguments: 61 arguments:
diff --git a/src/Wallabag/ImportBundle/Resources/config/redis.yml b/src/Wallabag/ImportBundle/Resources/config/redis.yml
index 091cdba0..40a6e224 100644
--- a/src/Wallabag/ImportBundle/Resources/config/redis.yml
+++ b/src/Wallabag/ImportBundle/Resources/config/redis.yml
@@ -126,6 +126,27 @@ services:
126 - "@event_dispatcher" 126 - "@event_dispatcher"
127 - "@logger" 127 - "@logger"
128 128
129 # elcurator
130 wallabag_import.queue.redis.elcurator:
131 class: Simpleue\Queue\RedisQueue
132 arguments:
133 - "@wallabag_core.redis.client"
134 - "wallabag.import.elcurator"
135
136 wallabag_import.producer.redis.elcurator:
137 class: Wallabag\ImportBundle\Redis\Producer
138 arguments:
139 - "@wallabag_import.queue.redis.elcurator"
140
141 wallabag_import.consumer.redis.elcurator:
142 class: Wallabag\ImportBundle\Consumer\RedisEntryConsumer
143 arguments:
144 - "@doctrine.orm.entity_manager"
145 - "@wallabag_user.user_repository"
146 - "@wallabag_import.elcurator.import"
147 - "@event_dispatcher"
148 - "@logger"
149
129 # firefox 150 # firefox
130 wallabag_import.queue.redis.firefox: 151 wallabag_import.queue.redis.firefox:
131 class: Simpleue\Queue\RedisQueue 152 class: Simpleue\Queue\RedisQueue
diff --git a/src/Wallabag/ImportBundle/Resources/config/services.yml b/src/Wallabag/ImportBundle/Resources/config/services.yml
index b224a6a2..d824da4a 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
@@ -54,6 +48,18 @@ services:
54 tags: 48 tags:
55 - { name: wallabag_import.import, alias: wallabag_v2 } 49 - { name: wallabag_import.import, alias: wallabag_v2 }
56 50
51 wallabag_import.elcurator.import:
52 class: Wallabag\ImportBundle\Import\ElcuratorImport
53 arguments:
54 - "@doctrine.orm.entity_manager"
55 - "@wallabag_core.content_proxy"
56 - "@wallabag_core.tags_assigner"
57 - "@event_dispatcher"
58 calls:
59 - [ setLogger, [ "@logger" ]]
60 tags:
61 - { name: wallabag_import.import, alias: elcurator }
62
57 wallabag_import.readability.import: 63 wallabag_import.readability.import:
58 class: Wallabag\ImportBundle\Import\ReadabilityImport 64 class: Wallabag\ImportBundle\Import\ReadabilityImport
59 arguments: 65 arguments:
@@ -112,3 +118,11 @@ services:
112 - [ setLogger, [ "@logger" ]] 118 - [ setLogger, [ "@logger" ]]
113 tags: 119 tags:
114 - { name: wallabag_import.import, alias: chrome } 120 - { name: wallabag_import.import, alias: chrome }
121
122 wallabag_import.command.import:
123 class: Wallabag\ImportBundle\Command\ImportCommand
124 tags: ['console.command']
125
126 wallabag_import.command.redis_worker:
127 class: Wallabag\ImportBundle\Command\RedisWorkerCommand
128 tags: ['console.command']
diff --git a/src/Wallabag/ImportBundle/Resources/views/Elcurator/index.html.twig b/src/Wallabag/ImportBundle/Resources/views/Elcurator/index.html.twig
new file mode 100644
index 00000000..e3a0d709
--- /dev/null
+++ b/src/Wallabag/ImportBundle/Resources/views/Elcurator/index.html.twig
@@ -0,0 +1,3 @@
1{% extends "WallabagImportBundle:WallabagV1:index.html.twig" %}
2
3{% block title %}{{ 'import.elcurator.page_title'|trans }}{% endblock %}
diff --git a/src/Wallabag/UserBundle/Controller/ManageController.php b/src/Wallabag/UserBundle/Controller/ManageController.php
index f3de656f..1122f8f0 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 {
@@ -116,8 +127,7 @@ class ManageController extends Controller
116 } 127 }
117 128
118 /** 129 /**
119 * @param Request $request 130 * @param int $page
120 * @param int $page
121 * 131 *
122 * @Route("/list/{page}", name="user_index", defaults={"page" = 1}) 132 * @Route("/list/{page}", name="user_index", defaults={"page" = 1})
123 * 133 *
@@ -135,8 +145,6 @@ class ManageController extends Controller
135 $form->handleRequest($request); 145 $form->handleRequest($request);
136 146
137 if ($form->isSubmitted() && $form->isValid()) { 147 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'] : ''); 148 $searchTerm = (isset($request->get('search_user')['term']) ? $request->get('search_user')['term'] : '');
141 149
142 $qb = $em->getRepository('WallabagUserBundle:User')->getQueryBuilderForSearch($searchTerm); 150 $qb = $em->getRepository('WallabagUserBundle:User')->getQueryBuilderForSearch($searchTerm);
@@ -161,7 +169,7 @@ class ManageController extends Controller
161 } 169 }
162 170
163 /** 171 /**
164 * Creates a form to delete a User entity. 172 * Create a form to delete a User entity.
165 * 173 *
166 * @param User $user The User entity 174 * @param User $user The User entity
167 * 175 *
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..aeab761d 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 {
@@ -182,8 +188,6 @@ class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterf
182 } 188 }
183 189
184 /** 190 /**
185 * @param Entry $entry
186 *
187 * @return User 191 * @return User
188 */ 192 */
189 public function addEntry(Entry $entry) 193 public function addEntry(Entry $entry)
@@ -233,54 +237,122 @@ class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterf
233 /** 237 /**
234 * @return bool 238 * @return bool
235 */ 239 */
236 public function isTwoFactorAuthentication() 240 public function isEmailTwoFactor()
237 { 241 {
238 return $this->twoFactorAuthentication; 242 return $this->emailTwoFactor;
239 } 243 }
240 244
241 /** 245 /**
242 * @param bool $twoFactorAuthentication 246 * @param bool $emailTwoFactor
243 */ 247 */
244 public function setTwoFactorAuthentication($twoFactorAuthentication) 248 public function setEmailTwoFactor($emailTwoFactor)
245 { 249 {
246 $this->twoFactorAuthentication = $twoFactorAuthentication; 250 $this->emailTwoFactor = $emailTwoFactor;
247 } 251 }
248 252
249 public function isEmailAuthEnabled() 253 /**
254 * Used in the user config form to be "like" the email option.
255 */
256 public function isGoogleTwoFactor()
250 { 257 {
251 return $this->twoFactorAuthentication; 258 return $this->isGoogleAuthenticatorEnabled();
252 } 259 }
253 260
254 public function getEmailAuthCode() 261 /**
262 * {@inheritdoc}
263 */
264 public function isEmailAuthEnabled(): bool
265 {
266 return $this->emailTwoFactor;
267 }
268
269 /**
270 * {@inheritdoc}
271 */
272 public function getEmailAuthCode(): string
255 { 273 {
256 return $this->authCode; 274 return $this->authCode;
257 } 275 }
258 276
259 public function setEmailAuthCode($authCode) 277 /**
278 * {@inheritdoc}
279 */
280 public function setEmailAuthCode(string $authCode): void
260 { 281 {
261 $this->authCode = $authCode; 282 $this->authCode = $authCode;
262 } 283 }
263 284
264 public function addTrustedComputer($token, \DateTime $validUntil) 285 /**
286 * {@inheritdoc}
287 */
288 public function getEmailAuthRecipient(): string
265 { 289 {
266 $this->trusted[$token] = $validUntil->format('r'); 290 return $this->email;
267 } 291 }
268 292
269 public function isTrustedComputer($token) 293 /**
294 * {@inheritdoc}
295 */
296 public function isGoogleAuthenticatorEnabled(): bool
270 { 297 {
271 if (isset($this->trusted[$token])) { 298 return $this->googleAuthenticatorSecret ? true : false;
272 $now = new \DateTime(); 299 }
273 $validUntil = new \DateTime($this->trusted[$token]);
274 300
275 return $now < $validUntil; 301 /**
276 } 302 * {@inheritdoc}
303 */
304 public function getGoogleAuthenticatorUsername(): string
305 {
306 return $this->username;
307 }
277 308
278 return false; 309 /**
310 * {@inheritdoc}
311 */
312 public function getGoogleAuthenticatorSecret(): string
313 {
314 return $this->googleAuthenticatorSecret;
315 }
316
317 /**
318 * {@inheritdoc}
319 */
320 public function setGoogleAuthenticatorSecret(?string $googleAuthenticatorSecret): void
321 {
322 $this->googleAuthenticatorSecret = $googleAuthenticatorSecret;
323 }
324
325 public function setBackupCodes(array $codes = null)
326 {
327 $this->backupCodes = $codes;
328 }
329
330 public function getBackupCodes()
331 {
332 return $this->backupCodes;
333 }
334
335 /**
336 * {@inheritdoc}
337 */
338 public function isBackupCode(string $code): bool
339 {
340 return false === $this->findBackupCode($code) ? false : true;
341 }
342
343 /**
344 * {@inheritdoc}
345 */
346 public function invalidateBackupCode(string $code): void
347 {
348 $key = $this->findBackupCode($code);
349
350 if (false !== $key) {
351 unset($this->backupCodes[$key]);
352 }
279 } 353 }
280 354
281 /** 355 /**
282 * @param Client $client
283 *
284 * @return User 356 * @return User
285 */ 357 */
286 public function addClient(Client $client) 358 public function addClient(Client $client)
@@ -309,4 +381,24 @@ class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterf
309 return $this->clients->first(); 381 return $this->clients->first();
310 } 382 }
311 } 383 }
384
385 /**
386 * Try to find a backup code from the list of backup codes of the current user.
387 *
388 * @param string $code Given code from the user
389 *
390 * @return string|false
391 */
392 private function findBackupCode(string $code)
393 {
394 foreach ($this->backupCodes as $key => $backupCode) {
395 // backup code are hashed using `password_hash`
396 // see ConfigController->otpAppAction
397 if (password_verify($code, $backupCode)) {
398 return $key;
399 }
400 }
401
402 return false;
403 }
312} 404}
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..03fad971 100644
--- a/src/Wallabag/UserBundle/Form/UserType.php
+++ b/src/Wallabag/UserBundle/Form/UserType.php
@@ -12,10 +12,6 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
12 12
13class UserType extends AbstractType 13class UserType extends AbstractType
14{ 14{
15 /**
16 * @param FormBuilderInterface $builder
17 * @param array $options
18 */
19 public function buildForm(FormBuilderInterface $builder, array $options) 15 public function buildForm(FormBuilderInterface $builder, array $options)
20 { 16 {
21 $builder 17 $builder
@@ -35,9 +31,14 @@ class UserType extends AbstractType
35 'required' => false, 31 'required' => false,
36 'label' => 'user.form.enabled_label', 32 'label' => 'user.form.enabled_label',
37 ]) 33 ])
38 ->add('twoFactorAuthentication', CheckboxType::class, [ 34 ->add('emailTwoFactor', CheckboxType::class, [
39 'required' => false, 35 'required' => false,
40 'label' => 'user.form.twofactor_label', 36 'label' => 'user.form.twofactor_email_label',
37 ])
38 ->add('googleTwoFactor', CheckboxType::class, [
39 'required' => false,
40 'label' => 'user.form.twofactor_google_label',
41 'mapped' => false,
41 ]) 42 ])
42 ->add('save', SubmitType::class, [ 43 ->add('save', SubmitType::class, [
43 'label' => 'user.form.save', 44 'label' => 'user.form.save',
@@ -45,9 +46,6 @@ class UserType extends AbstractType
45 ; 46 ;
46 } 47 }
47 48
48 /**
49 * @param OptionsResolver $resolver
50 */
51 public function configureOptions(OptionsResolver $resolver) 49 public function configureOptions(OptionsResolver $resolver)
52 { 50 {
53 $resolver->setDefaults([ 51 $resolver->setDefaults([
diff --git a/src/Wallabag/UserBundle/Mailer/AuthCodeMailer.php b/src/Wallabag/UserBundle/Mailer/AuthCodeMailer.php
index aed805c9..4eea444f 100644
--- a/src/Wallabag/UserBundle/Mailer/AuthCodeMailer.php
+++ b/src/Wallabag/UserBundle/Mailer/AuthCodeMailer.php
@@ -4,6 +4,7 @@ namespace Wallabag\UserBundle\Mailer;
4 4
5use Scheb\TwoFactorBundle\Mailer\AuthCodeMailerInterface; 5use Scheb\TwoFactorBundle\Mailer\AuthCodeMailerInterface;
6use Scheb\TwoFactorBundle\Model\Email\TwoFactorInterface; 6use Scheb\TwoFactorBundle\Model\Email\TwoFactorInterface;
7use Twig\Environment;
7 8
8/** 9/**
9 * Custom mailer for TwoFactorBundle email. 10 * Custom mailer for TwoFactorBundle email.
@@ -21,7 +22,7 @@ class AuthCodeMailer implements AuthCodeMailerInterface
21 /** 22 /**
22 * Twig to render the html's email. 23 * Twig to render the html's email.
23 * 24 *
24 * @var \Twig_Environment 25 * @var Environment
25 */ 26 */
26 private $twig; 27 private $twig;
27 28
@@ -56,14 +57,12 @@ class AuthCodeMailer implements AuthCodeMailerInterface
56 /** 57 /**
57 * Initialize the auth code mailer with the SwiftMailer object. 58 * Initialize the auth code mailer with the SwiftMailer object.
58 * 59 *
59 * @param \Swift_Mailer $mailer 60 * @param string $senderEmail
60 * @param \Twig_Environment $twig 61 * @param string $senderName
61 * @param string $senderEmail 62 * @param string $supportUrl wallabag support url
62 * @param string $senderName 63 * @param string $wallabagUrl wallabag instance url
63 * @param string $supportUrl wallabag support url
64 * @param string $wallabagUrl wallabag instance url
65 */ 64 */
66 public function __construct(\Swift_Mailer $mailer, \Twig_Environment $twig, $senderEmail, $senderName, $supportUrl, $wallabagUrl) 65 public function __construct(\Swift_Mailer $mailer, Environment $twig, $senderEmail, $senderName, $supportUrl, $wallabagUrl)
67 { 66 {
68 $this->mailer = $mailer; 67 $this->mailer = $mailer;
69 $this->twig = $twig; 68 $this->twig = $twig;
@@ -75,10 +74,8 @@ class AuthCodeMailer implements AuthCodeMailerInterface
75 74
76 /** 75 /**
77 * Send the auth code to the user via email. 76 * Send the auth code to the user via email.
78 *
79 * @param TwoFactorInterface $user
80 */ 77 */
81 public function sendAuthCode(TwoFactorInterface $user) 78 public function sendAuthCode(TwoFactorInterface $user): void
82 { 79 {
83 $template = $this->twig->loadTemplate('WallabagUserBundle:TwoFactor:email_auth_code.html.twig'); 80 $template = $this->twig->loadTemplate('WallabagUserBundle:TwoFactor:email_auth_code.html.twig');
84 81
@@ -97,7 +94,7 @@ class AuthCodeMailer implements AuthCodeMailerInterface
97 94
98 $message = new \Swift_Message(); 95 $message = new \Swift_Message();
99 $message 96 $message
100 ->setTo($user->getEmail()) 97 ->setTo($user->getEmailAuthRecipient())
101 ->setFrom($this->senderEmail, $this->senderName) 98 ->setFrom($this->senderEmail, $this->senderName)
102 ->setSubject($subject) 99 ->setSubject($subject)
103 ->setBody($bodyText, 'text/plain') 100 ->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..e15ed255 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,15 +10,20 @@
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, 'SchebTwoFactorBundle') }}</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">{{ "auth_code"|trans({}, 'SchebTwoFactorBundle') }}</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">{{ "trusted"|trans({}, 'SchebTwoFactorBundle') }}</label>
21 </div> 27 </div>
22 {% endif %} 28 {% endif %}
23 </div> 29 </div>
@@ -25,7 +31,7 @@
25 <div class="card-action center"> 31 <div class="card-action center">
26 <a href="{{ path('fos_user_security_logout') }}" class="waves-effect waves-light grey btn">{{ 'security.login.cancel'|trans }}</a> 32 <a href="{{ path('fos_user_security_logout') }}" class="waves-effect waves-light grey btn">{{ 'security.login.cancel'|trans }}</a>
27 <button class="btn waves-effect waves-light" type="submit" name="send"> 33 <button class="btn waves-effect waves-light" type="submit" name="send">
28 {{ "scheb_two_factor.login"|trans }} 34 {{ "login"|trans({}, 'SchebTwoFactorBundle') }}
29 <i class="material-icons right">send</i> 35 <i class="material-icons right">send</i>
30 </button> 36 </button>
31 </div> 37 </div>
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/TwoFactor/email_auth_code.html.twig b/src/Wallabag/UserBundle/Resources/views/TwoFactor/email_auth_code.html.twig
index ecc1d79a..cd5aaf40 100644
--- a/src/Wallabag/UserBundle/Resources/views/TwoFactor/email_auth_code.html.twig
+++ b/src/Wallabag/UserBundle/Resources/views/TwoFactor/email_auth_code.html.twig
@@ -74,7 +74,7 @@
74 74
75 <table cellpadding="0" cellspacing="0" border="0" align="center" id="card"> 75 <table cellpadding="0" cellspacing="0" border="0" align="center" id="card">
76 <tr> 76 <tr>
77 <td style="padding: 20px;" width="96px" valign="top"><img class="image_fix" src="{{ absolute_url(asset('wallassets/themes/_global/img/logo-other_themes.png')) }}" alt="logo" title="{{ wallabag_url }}" style="width: 96px; height: 96px;" /></td> 77 <td style="padding: 20px;" width="96px" valign="top"><img class="image_fix" src="{{ absolute_url(asset('wallassets/themes/_global/img/logo-square.svg')) }}" alt="logo" title="{{ wallabag_url }}" style="width: 96px; height: 96px;" /></td>
78 <td style="padding: 20px; padding-left: 0;" valign="top" id="cell_desc"> 78 <td style="padding: 20px; padding-left: 0;" valign="top" id="cell_desc">
79 <h1>wallabag</h1> 79 <h1>wallabag</h1>
80 <h5>{{ "auth_code.on"|trans({}, 'wallabag_user') }} {{ wallabag_url }}</h5> 80 <h5>{{ "auth_code.on"|trans({}, 'wallabag_user') }} {{ wallabag_url }}</h5>
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 %}