aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--.travis.yml2
-rw-r--r--Capfile2
-rw-r--r--Gemfile3
-rw-r--r--Gemfile.lock5
-rwxr-xr-xMakefile2
-rw-r--r--app/Resources/CraueConfigBundle/translations/CraueConfigBundle.pt.yml29
-rw-r--r--app/Resources/FOSUserBundle/translations/FOSUserBundle.fr.yml2
-rw-r--r--app/Resources/FOSUserBundle/translations/FOSUserBundle.pt.yml2
-rw-r--r--app/config/capistrano/deploy.rb9
-rw-r--r--app/config/config.yml4
-rw-r--r--docs/de/developer/api.rst3
-rw-r--r--docs/de/developer/rabbitmq.rst2
-rw-r--r--docs/de/developer/redis.rst2
-rw-r--r--docs/de/user/faq.rst7
-rw-r--r--docs/en/developer/api.rst3
-rw-r--r--docs/en/developer/rabbitmq.rst2
-rw-r--r--docs/en/developer/redis.rst2
-rw-r--r--docs/en/user/faq.rst7
-rw-r--r--docs/fr/developer/api.rst3
-rw-r--r--docs/fr/developer/rabbitmq.rst2
-rw-r--r--docs/fr/developer/redis.rst2
-rw-r--r--docs/fr/user/faq.rst7
-rw-r--r--scripts/dev.sh14
-rw-r--r--scripts/install.sh10
-rw-r--r--scripts/require.sh9
-rw-r--r--scripts/update.sh10
-rw-r--r--src/Wallabag/ApiBundle/Controller/EntryRestController.php374
-rw-r--r--src/Wallabag/ApiBundle/Controller/TagRestController.php171
-rw-r--r--src/Wallabag/ApiBundle/Controller/WallabagRestController.php641
-rw-r--r--src/Wallabag/ApiBundle/Resources/config/routing_rest.yml18
-rw-r--r--src/Wallabag/CoreBundle/Controller/ExportController.php23
-rw-r--r--src/Wallabag/CoreBundle/Controller/TagController.php1
-rw-r--r--src/Wallabag/CoreBundle/Helper/EntriesExport.php7
-rw-r--r--src/Wallabag/CoreBundle/Resources/config/services.yml3
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.da.yml3
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.de.yml3
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.en.yml3
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.es.yml3
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml3
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml725
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.it.yml3
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml3
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml3
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.pt.yml491
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml3
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml3
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/validators.fr.yml10
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/validators.pt.yml6
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/baggy/Entry/entries.html.twig24
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/common/Entry/_title.html.twig2
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/entries.html.twig24
-rw-r--r--src/Wallabag/ImportBundle/Command/ImportCommand.php3
-rw-r--r--src/Wallabag/UserBundle/Mailer/AuthCodeMailer.php10
-rw-r--r--src/Wallabag/UserBundle/Resources/config/services.yml3
-rw-r--r--src/Wallabag/UserBundle/Resources/translations/wallabag_user.fr.yml8
-rw-r--r--tests/Wallabag/ApiBundle/Controller/EntryRestControllerTest.php681
-rw-r--r--tests/Wallabag/ApiBundle/Controller/TagRestControllerTest.php162
-rw-r--r--tests/Wallabag/ApiBundle/Controller/WallabagRestControllerTest.php828
-rw-r--r--tests/Wallabag/CoreBundle/Controller/ExportControllerTest.php11
-rw-r--r--tests/Wallabag/UserBundle/Mailer/AuthCodeMailerTest.php12
60 files changed, 2477 insertions, 1936 deletions
diff --git a/.travis.yml b/.travis.yml
index c7bb05fb..a8f6a744 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -75,4 +75,6 @@ script:
75 - if [[ $VALIDATE_TRANSLATION_FILE = '' ]]; then phpunit -v ; fi; 75 - if [[ $VALIDATE_TRANSLATION_FILE = '' ]]; then phpunit -v ; fi;
76 - if [[ $CS_FIXER = run ]]; then php bin/php-cs-fixer fix src/ --verbose --dry-run ; fi; 76 - if [[ $CS_FIXER = run ]]; then php bin/php-cs-fixer fix src/ --verbose --dry-run ; fi;
77 - if [[ $VALIDATE_TRANSLATION_FILE = run ]]; then php bin/console lint:yaml src/Wallabag/CoreBundle/Resources/translations -v ; fi; 77 - if [[ $VALIDATE_TRANSLATION_FILE = run ]]; then php bin/console lint:yaml src/Wallabag/CoreBundle/Resources/translations -v ; fi;
78 - if [[ $VALIDATE_TRANSLATION_FILE = run ]]; then php bin/console lint:yaml app/Resources/CraueConfigBundle/translations -v ; fi;
79 - if [[ $VALIDATE_TRANSLATION_FILE = run ]]; then php bin/console lint:yaml app/Resources/FOSUserBundle/translations -v ; fi;
78 - if [[ $ASSETS = build ]]; then ./node_modules/grunt-cli/bin/grunt tests; fi; 80 - if [[ $ASSETS = build ]]; then ./node_modules/grunt-cli/bin/grunt tests; fi;
diff --git a/Capfile b/Capfile
index b80a5646..cc807112 100644
--- a/Capfile
+++ b/Capfile
@@ -7,6 +7,8 @@ require 'capistrano/setup'
7# Include default deployment tasks 7# Include default deployment tasks
8require 'capistrano/deploy' 8require 'capistrano/deploy'
9 9
10require 'capistrano/composer'
11require 'capistrano/file-permissions'
10require 'capistrano/symfony' 12require 'capistrano/symfony'
11 13
12# Load custom tasks from `lib/capistrano/tasks` if you have any defined 14# Load custom tasks from `lib/capistrano/tasks` if you have any defined
diff --git a/Gemfile b/Gemfile
index 31f887a9..233be899 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,5 +1,6 @@
1source "https://rubygems.org" 1source "https://rubygems.org"
2 2
3gem 'capistrano', '~> 3.4' 3gem 'capistrano', '~> 3.4'
4gem 'capistrano-composer'
4gem 'capistrano-symfony', '~> 1.0.0.rc1' 5gem 'capistrano-symfony', '~> 1.0.0.rc1'
5gem 'capistrano-composer', '~> 0.0.3' 6gem 'capistrano-file-permissions'
diff --git a/Gemfile.lock b/Gemfile.lock
index aebbeba2..7b13b399 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -29,8 +29,9 @@ PLATFORMS
29 29
30DEPENDENCIES 30DEPENDENCIES
31 capistrano (~> 3.4) 31 capistrano (~> 3.4)
32 capistrano-composer (~> 0.0.3) 32 capistrano-composer
33 capistrano-file-permissions
33 capistrano-symfony (~> 1.0.0.rc1) 34 capistrano-symfony (~> 1.0.0.rc1)
34 35
35BUNDLED WITH 36BUNDLED WITH
36 1.11.2 37 1.13.5
diff --git a/Makefile b/Makefile
index 1df4d632..83c5f37a 100755
--- a/Makefile
+++ b/Makefile
@@ -41,6 +41,6 @@ travis: ## Make some stuff for Travis-CI
41deploy: ## Deploy wallabag 41deploy: ## Deploy wallabag
42 @bundle exec cap staging deploy 42 @bundle exec cap staging deploy
43 43
44.PHONY: help clean install update build test release travis deploy 44.PHONY: help clean install update build test release travis deploy run dev
45 45
46.DEFAULT_GOAL := install 46.DEFAULT_GOAL := install
diff --git a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.pt.yml b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.pt.yml
new file mode 100644
index 00000000..e8260422
--- /dev/null
+++ b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.pt.yml
@@ -0,0 +1,29 @@
1download_pictures: Download imagens no seu servidor
2carrot: Habilitar compartilhamento para o Carrot
3diaspora_url: URL Diaspora, se o serviço está habilitado
4export_epub: Habilita exportação para ePub
5export_mobi: Habilita exportação para .mobi
6export_pdf: Habilita exportação para PDF
7export_csv: Habilita exportação para CSV
8export_json: Habilita exportação para JSON
9export_txt: Habilita exportação para TXT
10export_xml: Habilita exportação para XML
11pocket_consumer_key: Chave de consumidor do Pocket para importar conteúdo (https://getpocket.com/developer/docs/authentication)
12shaarli_url: URL Shaarli, se o serviço está habilitado
13share_diaspora: Habilitar compartilhamento para o Diaspora
14share_mail: Habilitar compartilhamento por e-mail
15share_shaarli: Habilitar compartilhamento para o Shaarli
16share_twitter: Habilitar compartilhamento para o Twitter
17show_printlink: Mostrar um link para imprimir o conteúdo
18wallabag_support_url: URL de Suporte do wallabag
19wallabag_url: URL de *sua* instância do wallabag
20entry: "artigo"
21export: "exportar"
22import: "importar"
23misc: "misc"
24modify_settings: "aplicar"
25piwik_host: Host de seu website Piwik
26piwik_site_id: ID de seu website Piwik
27piwik_enabled: Habilitar Piwik
28demo_mode_enabled: "Habilitar modo demo? (somente usado para o demo público do wallabag)"
29demo_mode_username: "Usuário demo"
diff --git a/app/Resources/FOSUserBundle/translations/FOSUserBundle.fr.yml b/app/Resources/FOSUserBundle/translations/FOSUserBundle.fr.yml
index 1ccad137..1c5ea640 100644
--- a/app/Resources/FOSUserBundle/translations/FOSUserBundle.fr.yml
+++ b/app/Resources/FOSUserBundle/translations/FOSUserBundle.fr.yml
@@ -1,2 +1,2 @@
1Login: "Se connecter" 1Login: "Se connecter"
2Enter your email address below and we'll send you password reset instructions.: "Renseignez votre adresse email, nous vous enverrons les instructions pour réinitialiser votre mot de passe." 2Enter your email address below and we'll send you password reset instructions.: "Renseignez votre adresse courriel, nous vous enverrons les instructions pour réinitialiser votre mot de passe."
diff --git a/app/Resources/FOSUserBundle/translations/FOSUserBundle.pt.yml b/app/Resources/FOSUserBundle/translations/FOSUserBundle.pt.yml
new file mode 100644
index 00000000..85eadfd8
--- /dev/null
+++ b/app/Resources/FOSUserBundle/translations/FOSUserBundle.pt.yml
@@ -0,0 +1,2 @@
1Login: "Login"
2Enter your email address below and we'll send you password reset instructions.: "Digite seu endereço de e-mail para enviarmos as instruções de recupeção de sua senha."
diff --git a/app/config/capistrano/deploy.rb b/app/config/capistrano/deploy.rb
index f15eef30..fee04620 100644
--- a/app/config/capistrano/deploy.rb
+++ b/app/config/capistrano/deploy.rb
@@ -1,9 +1,4 @@
1# config valid only for current version of Capistrano 1# config valid only for current version of Capistrano
2lock '3.4.0'
3
4set :log_path, "var/logs"
5set :cache_path, "var/cache"
6set :symfony_console_path, 'bin/console'
7 2
8set :application, 'wallabag' 3set :application, 'wallabag'
9set :repo_url, 'git@github.com:wallabag/wallabag.git' 4set :repo_url, 'git@github.com:wallabag/wallabag.git'
@@ -11,8 +6,6 @@ set :repo_url, 'git@github.com:wallabag/wallabag.git'
11set :ssh_user, 'framasoft_bag' 6set :ssh_user, 'framasoft_bag'
12server '78.46.248.87', user: fetch(:ssh_user), roles: %w{web app db} 7server '78.46.248.87', user: fetch(:ssh_user), roles: %w{web app db}
13 8
14set :scm, :git
15
16set :format, :pretty 9set :format, :pretty
17set :log_level, :info 10set :log_level, :info
18# set :log_level, :debug 11# set :log_level, :debug
@@ -23,4 +16,4 @@ set :linked_files, %w{app/config/parameters.yml}
23set :linked_dirs, [fetch(:log_path), "var/sessions", "web/uploads", "data"] 16set :linked_dirs, [fetch(:log_path), "var/sessions", "web/uploads", "data"]
24set :keep_releases, 3 17set :keep_releases, 3
25 18
26after 'deploy:finishing', 'deploy:cleanup' 19after 'deploy:updated', 'symfony:cache:clear'
diff --git a/app/config/config.yml b/app/config/config.yml
index dfb0e3b2..7f24244d 100644
--- a/app/config/config.yml
+++ b/app/config/config.yml
@@ -44,13 +44,15 @@ wallabag_core:
44 es: 'Español' 44 es: 'Español'
45 oc: 'Occitan' 45 oc: 'Occitan'
46 it: 'Italiano' 46 it: 'Italiano'
47 pt: 'Português'
47 items_on_page: 12 48 items_on_page: 12
48 theme: material 49 theme: material
49 language: '%locale%' 50 language: '%locale%'
50 rss_limit: 50 51 rss_limit: 50
51 reading_speed: 1 52 reading_speed: 1
52 cache_lifetime: 10 53 cache_lifetime: 10
53 fetching_error_message: "wallabag can't retrieve contents for this article. Please report this issue to us." 54 fetching_error_message: |
55 wallabag can't retrieve contents for this article. Please <a href="http://doc.wallabag.org/en/master/user/errors_during_fetching.html#how-can-i-help-to-fix-that">troubleshoot this issue</a>.
54 56
55wallabag_user: 57wallabag_user:
56 registration_enabled: "%fosuser_registration%" 58 registration_enabled: "%fosuser_registration%"
diff --git a/docs/de/developer/api.rst b/docs/de/developer/api.rst
index f8911181..bb21154d 100644
--- a/docs/de/developer/api.rst
+++ b/docs/de/developer/api.rst
@@ -264,7 +264,8 @@ Drittanbieter Ressourcen
264 264
265Einige Applikationen oder Bibliotheken nutzen unsere API. Hier ist eine nicht abschließende Aufzählung von ihnen: 265Einige Applikationen oder Bibliotheken nutzen unsere API. Hier ist eine nicht abschließende Aufzählung von ihnen:
266 266
267- `Java wrapper for the wallabag API <https://github.com/Strubbl/wallabag-java>`_ von Strubbl. 267- `Java wrapper for the wallabag API <https://github.com/Strubbl/jWallabag>`_ von Strubbl.
268- `.NET library for the wallabag v2 API <https://github.com/jlnostr/wallabag-api>`_ von Julian Oster. 268- `.NET library for the wallabag v2 API <https://github.com/jlnostr/wallabag-api>`_ von Julian Oster.
269- `Python API for wallabag <https://github.com/foxmask/wallabag_api>`_ von FoxMaSk, für sein Projekt `Trigger Happy <https://blog.trigger-happy.eu/>`_. 269- `Python API for wallabag <https://github.com/foxmask/wallabag_api>`_ von FoxMaSk, für sein Projekt `Trigger Happy <https://blog.trigger-happy.eu/>`_.
270- `A plugin <https://github.com/joshp23/ttrss-to-wallabag-v2>`_ entworfen für `Tiny Tiny RSS <https://tt-rss.org/gitlab/fox/tt-rss/wikis/home>`_, das die wallabag v2 API nutzt. Von Josh Panter. 270- `A plugin <https://github.com/joshp23/ttrss-to-wallabag-v2>`_ entworfen für `Tiny Tiny RSS <https://tt-rss.org/gitlab/fox/tt-rss/wikis/home>`_, das die wallabag v2 API nutzt. Von Josh Panter.
271- `Golang wrapper for the wallabag API <https://github.com/Strubbl/wallabago>`_ von Strubbl, für sein Projekt `wallabag-stats Graph <https://github.com/Strubbl/wallabag-stats>`_.
diff --git a/docs/de/developer/rabbitmq.rst b/docs/de/developer/rabbitmq.rst
index f81e07e3..143b64a1 100644
--- a/docs/de/developer/rabbitmq.rst
+++ b/docs/de/developer/rabbitmq.rst
@@ -37,7 +37,7 @@ RabbitMQ stoppen
37Konfigure RabbitMQ in wallabag 37Konfigure RabbitMQ in wallabag
38------------------------------ 38------------------------------
39 39
40Bearbeite die Datei ``parameters.yml``, um die RabbitMQ Konfiguration einzurichten. Die Standardkonfiguration sollte ok sein: 40Bearbeite die Datei ``app/config/parameters.yml``, um die RabbitMQ Konfiguration einzurichten. Die Standardkonfiguration sollte ok sein:
41 41
42.. code:: yaml 42.. code:: yaml
43 43
diff --git a/docs/de/developer/redis.rst b/docs/de/developer/redis.rst
index 57b41550..2505bf24 100644
--- a/docs/de/developer/redis.rst
+++ b/docs/de/developer/redis.rst
@@ -28,7 +28,7 @@ Der Redis Service läuft eventuell schon direkt nach der Installation. Falls nic
28Konfigure Redis in wallabag 28Konfigure Redis in wallabag
29--------------------------- 29---------------------------
30 30
31Bearbeite die Datei ``parameters.yml``, um die RabbitMQ Konfiguration einzurichten. Die Standardkonfiguration sollte ok sein: 31Bearbeite die Datei ``app/config/parameters.yml``, um die RabbitMQ Konfiguration einzurichten. Die Standardkonfiguration sollte ok sein:
32 32
33.. code:: yaml 33.. code:: yaml
34 34
diff --git a/docs/de/user/faq.rst b/docs/de/user/faq.rst
index 1a199c1c..c14cb3ef 100644
--- a/docs/de/user/faq.rst
+++ b/docs/de/user/faq.rst
@@ -43,3 +43,10 @@ Ich habe mein Passwort vergessen
43 43
44Du kannst dein Passwort zurücksetzen, indem du auf den Link ``Kennwort vergessen?`` auf der Loginseite klickst. Fülle dann das Formular mit deiner E-Mail-Adresse oder deinem Nutzernamen aus 44Du kannst dein Passwort zurücksetzen, indem du auf den Link ``Kennwort vergessen?`` auf der Loginseite klickst. Fülle dann das Formular mit deiner E-Mail-Adresse oder deinem Nutzernamen aus
45und du wirst eine E-Mail zum Passwort zurücksetzen erhalten. 45und du wirst eine E-Mail zum Passwort zurücksetzen erhalten.
46
47Ich erhalte den Fehler ``failed to load external entity``, wenn ich wallabag installiere
48----------------------------------------------------------------------------------------
49
50Wie `hier <https://github.com/wallabag/wallabag/issues/2529>`_ beschrieben, bearbeite bitte deine Datei ``web/app.php`` und füge ihr diese Zeile ``libxml_disable_entity_loader(false);`` in Zeile 5 hinzu.
51
52Dies ist ein Doctrine / PHP Fehler - nichts, woran wir etwas ändern können.
diff --git a/docs/en/developer/api.rst b/docs/en/developer/api.rst
index 4828cddd..b6c9ed3f 100644
--- a/docs/en/developer/api.rst
+++ b/docs/en/developer/api.rst
@@ -263,7 +263,8 @@ Third party resources
263 263
264Some applications or libraries use our API. Here is a non-exhaustive list of them: 264Some applications or libraries use our API. Here is a non-exhaustive list of them:
265 265
266- `Java wrapper for the wallabag API <https://github.com/Strubbl/wallabag-java>`_ by Strubbl. 266- `Java wrapper for the wallabag API <https://github.com/Strubbl/jWallabag>`_ by Strubbl.
267- `.NET library for the wallabag v2 API <https://github.com/jlnostr/wallabag-api>`_ by Julian Oster. 267- `.NET library for the wallabag v2 API <https://github.com/jlnostr/wallabag-api>`_ by Julian Oster.
268- `Python API for wallabag <https://github.com/foxmask/wallabag_api>`_ by FoxMaSk, for his project `Trigger Happy <https://blog.trigger-happy.eu/>`_. 268- `Python API for wallabag <https://github.com/foxmask/wallabag_api>`_ by FoxMaSk, for his project `Trigger Happy <https://blog.trigger-happy.eu/>`_.
269- `A plugin <https://github.com/joshp23/ttrss-to-wallabag-v2>`_ designed for `Tiny Tiny RSS <https://tt-rss.org/gitlab/fox/tt-rss/wikis/home>`_ that makes use of the wallabag v2 API. By Josh Panter. 269- `A plugin <https://github.com/joshp23/ttrss-to-wallabag-v2>`_ designed for `Tiny Tiny RSS <https://tt-rss.org/gitlab/fox/tt-rss/wikis/home>`_ that makes use of the wallabag v2 API. By Josh Panter.
270- `Golang wrapper for the wallabag API <https://github.com/Strubbl/wallabago>`_ by Strubbl, for his project `wallabag-stats graph <https://github.com/Strubbl/wallabag-stats>`_.
diff --git a/docs/en/developer/rabbitmq.rst b/docs/en/developer/rabbitmq.rst
index 673228e9..7ee8a5ce 100644
--- a/docs/en/developer/rabbitmq.rst
+++ b/docs/en/developer/rabbitmq.rst
@@ -37,7 +37,7 @@ Stop RabbitMQ
37Configure RabbitMQ in wallabag 37Configure RabbitMQ in wallabag
38------------------------------ 38------------------------------
39 39
40Edit your ``parameters.yml`` file to edit RabbitMQ configuration. The default one should be ok: 40Edit your ``app/config/parameters.yml`` file to edit RabbitMQ configuration. The default one should be ok:
41 41
42.. code:: yaml 42.. code:: yaml
43 43
diff --git a/docs/en/developer/redis.rst b/docs/en/developer/redis.rst
index 2e2bbbea..ea084e66 100644
--- a/docs/en/developer/redis.rst
+++ b/docs/en/developer/redis.rst
@@ -28,7 +28,7 @@ The server might be already running after installing, if not you can launch it u
28Configure Redis in wallabag 28Configure Redis in wallabag
29--------------------------- 29---------------------------
30 30
31Edit your ``parameters.yml`` file to edit Redis configuration. The default one should be ok: 31Edit your ``app/config/parameters.yml`` file to edit Redis configuration. The default one should be ok:
32 32
33.. code:: yaml 33.. code:: yaml
34 34
diff --git a/docs/en/user/faq.rst b/docs/en/user/faq.rst
index 61303604..0f995ce5 100644
--- a/docs/en/user/faq.rst
+++ b/docs/en/user/faq.rst
@@ -46,3 +46,10 @@ I forgot my password
46You can reset your password by clicking on ``Forgot your password?`` link, 46You can reset your password by clicking on ``Forgot your password?`` link,
47on the login page. Then, fill the form with your email address or your username, 47on the login page. Then, fill the form with your email address or your username,
48you'll receive an email to reset your password. 48you'll receive an email to reset your password.
49
50I've got the ``failed to load external entity`` error when I try to install wallabag
51------------------------------------------------------------------------------------
52
53As described `here <https://github.com/wallabag/wallabag/issues/2529>`_, please edit your ``web/app.php`` file and add this line: ``libxml_disable_entity_loader(false);`` on line 5.
54
55This is a Doctrine / PHP bug, nothing we can do about it.
diff --git a/docs/fr/developer/api.rst b/docs/fr/developer/api.rst
index a0710a96..8a6e2a13 100644
--- a/docs/fr/developer/api.rst
+++ b/docs/fr/developer/api.rst
@@ -263,7 +263,8 @@ Ressources tierces
263 263
264Certaines applications ou bibliothèques utilisent notre API. En voici une liste non exhaustive : 264Certaines applications ou bibliothèques utilisent notre API. En voici une liste non exhaustive :
265 265
266- `Java wrapper for the wallabag API <https://github.com/Strubbl/wallabag-java>`_ par Strubbl. 266- `Java wrapper for the wallabag API <https://github.com/Strubbl/jWallabag>`_ par Strubbl.
267- `.NET library for the wallabag v2 API <https://github.com/jlnostr/wallabag-api>`_ par Julian Oster. 267- `.NET library for the wallabag v2 API <https://github.com/jlnostr/wallabag-api>`_ par Julian Oster.
268- `Python API for wallabag <https://github.com/foxmask/wallabag_api>`_ par FoxMaSk, pour son projet `Trigger Happy <https://blog.trigger-happy.eu/>`_. 268- `Python API for wallabag <https://github.com/foxmask/wallabag_api>`_ par FoxMaSk, pour son projet `Trigger Happy <https://blog.trigger-happy.eu/>`_.
269- `Un plugin <https://github.com/joshp23/ttrss-to-wallabag-v2>`_ conçu pour `Tiny Tiny RSS <https://tt-rss.org/gitlab/fox/tt-rss/wikis/home>`_ qui utilise l'API wallabag v2. Par Josh Panter. 269- `Un plugin <https://github.com/joshp23/ttrss-to-wallabag-v2>`_ conçu pour `Tiny Tiny RSS <https://tt-rss.org/gitlab/fox/tt-rss/wikis/home>`_ qui utilise l'API wallabag v2. Par Josh Panter.
270- `Golang wrapper for the wallabag API <https://github.com/Strubbl/wallabago>`_ par Strubbl, pour son projet `wallabag-stats graphe <https://github.com/Strubbl/wallabag-stats>`_.
diff --git a/docs/fr/developer/rabbitmq.rst b/docs/fr/developer/rabbitmq.rst
index 92db5a28..b534a48b 100644
--- a/docs/fr/developer/rabbitmq.rst
+++ b/docs/fr/developer/rabbitmq.rst
@@ -37,7 +37,7 @@ Arrêter RabbitMQ
37Configurer RabbitMQ dans wallabag 37Configurer RabbitMQ dans wallabag
38--------------------------------- 38---------------------------------
39 39
40Modifiez votre fichier ``parameters.yml`` pour éditer la configuration RabbitMQ. Celle par défaut devrait convenir : 40Modifiez votre fichier ``app/config/parameters.yml`` pour éditer la configuration RabbitMQ. Celle par défaut devrait convenir :
41 41
42.. code:: yaml 42.. code:: yaml
43 43
diff --git a/docs/fr/developer/redis.rst b/docs/fr/developer/redis.rst
index 8a212e8a..58204d57 100644
--- a/docs/fr/developer/redis.rst
+++ b/docs/fr/developer/redis.rst
@@ -28,7 +28,7 @@ Le serveur devrait déjà être démarré après l'installation. Si ce n'est pas
28Configurer Redis dans wallabag 28Configurer Redis dans wallabag
29------------------------------- 29-------------------------------
30 30
31Modifiez votre fichier ``parameters.yml`` pour éditer la configuration Redis. Celle par défaut devrait convenir : 31Modifiez votre fichier ``app/config/parameters.yml`` pour éditer la configuration Redis. Celle par défaut devrait convenir :
32 32
33.. code:: yaml 33.. code:: yaml
34 34
diff --git a/docs/fr/user/faq.rst b/docs/fr/user/faq.rst
index e220c8a7..49aa94ba 100644
--- a/docs/fr/user/faq.rst
+++ b/docs/fr/user/faq.rst
@@ -33,3 +33,10 @@ J'ai oublié mon mot de passe
33Vous pouvez réinitialiser votre mot de passe en cliquant sur ``Mot de passe oublié ?``, 33Vous pouvez réinitialiser votre mot de passe en cliquant sur ``Mot de passe oublié ?``,
34sur la page de connexion. Ensuite, renseignez votre adresse email ou votre nom d'utilisateur, 34sur la page de connexion. Ensuite, renseignez votre adresse email ou votre nom d'utilisateur,
35un email vous sera envoyé. 35un email vous sera envoyé.
36
37J'ai l'erreur ``failed to load external entity`` quand j'essaie d'installer wallabag
38------------------------------------------------------------------------------------
39
40Comme décrit `ici <https://github.com/wallabag/wallabag/issues/2529>`_, modifiez le fichier ``web/app.php`` et ajoutez la ligne ``libxml_disable_entity_loader(false);`` à la ligne 5.
41
42C'est un bug lié à PHP et Doctrine, nous ne pouvons rien faire de notre côté.
diff --git a/scripts/dev.sh b/scripts/dev.sh
index 9b89da35..0703ced1 100644
--- a/scripts/dev.sh
+++ b/scripts/dev.sh
@@ -1,7 +1,13 @@
1#! /usr/bin/env bash 1#!/usr/bin/env bash
2# You can execute this file to install wallabag dev environmnet 2# You can execute this file to install wallabag dev environment
3# eg: `sh install.sh prod` 3# eg: `sh dev.sh`
4 4
5composer install 5COMPOSER_COMMAND='composer'
6
7DIR="${BASH_SOURCE}"
8if [ ! -d "$DIR" ]; then DIR="$PWD/scripts"; fi
9. "$DIR/require.sh"
10
11$COMPOSER_COMMAND install
6php bin/console wallabag:install 12php bin/console wallabag:install
7php bin/console server:run 13php bin/console server:run
diff --git a/scripts/install.sh b/scripts/install.sh
index 54d0bb78..62a46f4f 100644
--- a/scripts/install.sh
+++ b/scripts/install.sh
@@ -1,10 +1,16 @@
1#! /usr/bin/env bash 1#!/usr/bin/env bash
2# You can execute this file to install wallabag 2# You can execute this file to install wallabag
3# eg: `sh install.sh prod` 3# eg: `sh install.sh prod`
4 4
5COMPOSER_COMMAND='composer'
6
7DIR="${BASH_SOURCE}"
8if [ ! -d "$DIR" ]; then DIR="$PWD/scripts"; fi
9. "$DIR/require.sh"
10
5ENV=$1 11ENV=$1
6TAG=$(git describe --tags $(git rev-list --tags --max-count=1)) 12TAG=$(git describe --tags $(git rev-list --tags --max-count=1))
7 13
8git checkout $TAG 14git checkout $TAG
9SYMFONY_ENV=$ENV composer install --no-dev -o --prefer-dist 15SYMFONY_ENV=$ENV $COMPOSER_COMMAND install --no-dev -o --prefer-dist
10php bin/console wallabag:install --env=$ENV 16php bin/console wallabag:install --env=$ENV
diff --git a/scripts/require.sh b/scripts/require.sh
new file mode 100644
index 00000000..ddfb3dd2
--- /dev/null
+++ b/scripts/require.sh
@@ -0,0 +1,9 @@
1#! /usr/bin/env bash
2# File used to check dependencies
3
4if [ ! -f composer.phar ]; then
5 echo "composer.phar not found, we'll see if composer is installed globally."
6 command -v composer >/dev/null 2>&1 || { echo >&2 "wallabag requires composer but it's not installed (see http://doc.wallabag.org/en/master/user/installation.html). Aborting."; exit 1; }
7else
8 COMPOSER_COMMAND='composer.phar'
9fi
diff --git a/scripts/update.sh b/scripts/update.sh
index b920a829..f43c4f24 100644
--- a/scripts/update.sh
+++ b/scripts/update.sh
@@ -1,7 +1,13 @@
1#! /usr/bin/env bash 1#!/usr/bin/env bash
2# You can execute this file to update wallabag 2# You can execute this file to update wallabag
3# eg: `sh update.sh prod` 3# eg: `sh update.sh prod`
4 4
5COMPOSER_COMMAND='composer'
6
7DIR="${BASH_SOURCE}"
8if [ ! -d "$DIR" ]; then DIR="$PWD/scripts"; fi
9. "$DIR/require.sh"
10
5ENV=$1 11ENV=$1
6TAG=$(git describe --tags $(git rev-list --tags --max-count=1)) 12TAG=$(git describe --tags $(git rev-list --tags --max-count=1))
7 13
@@ -9,5 +15,5 @@ rm -rf var/cache/*
9git fetch origin 15git fetch origin
10git fetch --tags 16git fetch --tags
11git checkout $TAG --force 17git checkout $TAG --force
12SYMFONY_ENV=$ENV composer install --no-dev -o --prefer-dist 18SYMFONY_ENV=$ENV $COMPOSER_COMMAND install --no-dev -o --prefer-dist
13php bin/console cache:clear --env=$ENV 19php bin/console cache:clear --env=$ENV
diff --git a/src/Wallabag/ApiBundle/Controller/EntryRestController.php b/src/Wallabag/ApiBundle/Controller/EntryRestController.php
new file mode 100644
index 00000000..b3622c62
--- /dev/null
+++ b/src/Wallabag/ApiBundle/Controller/EntryRestController.php
@@ -0,0 +1,374 @@
1<?php
2
3namespace Wallabag\ApiBundle\Controller;
4
5use Hateoas\Configuration\Route;
6use Hateoas\Representation\Factory\PagerfantaFactory;
7use Nelmio\ApiDocBundle\Annotation\ApiDoc;
8use Symfony\Component\HttpFoundation\Request;
9use Symfony\Component\HttpFoundation\JsonResponse;
10use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
11use Wallabag\CoreBundle\Entity\Entry;
12use Wallabag\CoreBundle\Entity\Tag;
13use Wallabag\CoreBundle\Event\EntrySavedEvent;
14use Wallabag\CoreBundle\Event\EntryDeletedEvent;
15
16class EntryRestController extends WallabagRestController
17{
18 /**
19 * Check if an entry exist by url.
20 *
21 * @ApiDoc(
22 * parameters={
23 * {"name"="url", "dataType"="string", "required"=true, "format"="An url", "description"="Url to check if it exists"},
24 * {"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"}
25 * }
26 * )
27 *
28 * @return JsonResponse
29 */
30 public function getEntriesExistsAction(Request $request)
31 {
32 $this->validateAuthentication();
33
34 $urls = $request->query->get('urls', []);
35
36 // handle multiple urls first
37 if (!empty($urls)) {
38 $results = [];
39 foreach ($urls as $url) {
40 $res = $this->getDoctrine()
41 ->getRepository('WallabagCoreBundle:Entry')
42 ->findByUrlAndUserId($url, $this->getUser()->getId());
43
44 $results[$url] = false === $res ? false : true;
45 }
46
47 $json = $this->get('serializer')->serialize($results, 'json');
48
49 return (new JsonResponse())->setJson($json);
50 }
51
52 // let's see if it is a simple url?
53 $url = $request->query->get('url', '');
54
55 if (empty($url)) {
56 throw $this->createAccessDeniedException('URL is empty?, logged user id: '.$this->getUser()->getId());
57 }
58
59 $res = $this->getDoctrine()
60 ->getRepository('WallabagCoreBundle:Entry')
61 ->findByUrlAndUserId($url, $this->getUser()->getId());
62
63 $exists = false === $res ? false : true;
64
65 $json = $this->get('serializer')->serialize(['exists' => $exists], 'json');
66
67 return (new JsonResponse())->setJson($json);
68 }
69
70 /**
71 * Retrieve all entries. It could be filtered by many options.
72 *
73 * @ApiDoc(
74 * parameters={
75 * {"name"="archive", "dataType"="integer", "required"=false, "format"="1 or 0, all entries by default", "description"="filter by archived status."},
76 * {"name"="starred", "dataType"="integer", "required"=false, "format"="1 or 0, all entries by default", "description"="filter by starred status."},
77 * {"name"="sort", "dataType"="string", "required"=false, "format"="'created' or 'updated', default 'created'", "description"="sort entries by date."},
78 * {"name"="order", "dataType"="string", "required"=false, "format"="'asc' or 'desc', default 'desc'", "description"="order of sort."},
79 * {"name"="page", "dataType"="integer", "required"=false, "format"="default '1'", "description"="what page you want."},
80 * {"name"="perPage", "dataType"="integer", "required"=false, "format"="default'30'", "description"="results per page."},
81 * {"name"="tags", "dataType"="string", "required"=false, "format"="api,rest", "description"="a list of tags url encoded. Will returns entries that matches ALL tags."},
82 * {"name"="since", "dataType"="integer", "required"=false, "format"="default '0'", "description"="The timestamp since when you want entries updated."},
83 * }
84 * )
85 *
86 * @return JsonResponse
87 */
88 public function getEntriesAction(Request $request)
89 {
90 $this->validateAuthentication();
91
92 $isArchived = (null === $request->query->get('archive')) ? null : (bool) $request->query->get('archive');
93 $isStarred = (null === $request->query->get('starred')) ? null : (bool) $request->query->get('starred');
94 $sort = $request->query->get('sort', 'created');
95 $order = $request->query->get('order', 'desc');
96 $page = (int) $request->query->get('page', 1);
97 $perPage = (int) $request->query->get('perPage', 30);
98 $tags = $request->query->get('tags', '');
99 $since = $request->query->get('since', 0);
100
101 $pager = $this->getDoctrine()
102 ->getRepository('WallabagCoreBundle:Entry')
103 ->findEntries($this->getUser()->getId(), $isArchived, $isStarred, $sort, $order, $since, $tags);
104
105 $pager->setCurrentPage($page);
106 $pager->setMaxPerPage($perPage);
107
108 $pagerfantaFactory = new PagerfantaFactory('page', 'perPage');
109 $paginatedCollection = $pagerfantaFactory->createRepresentation(
110 $pager,
111 new Route(
112 'api_get_entries',
113 [
114 'archive' => $isArchived,
115 'starred' => $isStarred,
116 'sort' => $sort,
117 'order' => $order,
118 'page' => $page,
119 'perPage' => $perPage,
120 'tags' => $tags,
121 'since' => $since,
122 ],
123 UrlGeneratorInterface::ABSOLUTE_URL
124 )
125 );
126
127 $json = $this->get('serializer')->serialize($paginatedCollection, 'json');
128
129 return (new JsonResponse())->setJson($json);
130 }
131
132 /**
133 * Retrieve a single entry.
134 *
135 * @ApiDoc(
136 * requirements={
137 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
138 * }
139 * )
140 *
141 * @return JsonResponse
142 */
143 public function getEntryAction(Entry $entry)
144 {
145 $this->validateAuthentication();
146 $this->validateUserAccess($entry->getUser()->getId());
147
148 $json = $this->get('serializer')->serialize($entry, 'json');
149
150 return (new JsonResponse())->setJson($json);
151 }
152
153 /**
154 * Create an entry.
155 *
156 * @ApiDoc(
157 * parameters={
158 * {"name"="url", "dataType"="string", "required"=true, "format"="http://www.test.com/article.html", "description"="Url for the entry."},
159 * {"name"="title", "dataType"="string", "required"=false, "description"="Optional, we'll get the title from the page."},
160 * {"name"="tags", "dataType"="string", "required"=false, "format"="tag1,tag2,tag3", "description"="a comma-separated list of tags."},
161 * {"name"="starred", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="entry already starred"},
162 * {"name"="archive", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="entry already archived"},
163 * }
164 * )
165 *
166 * @return JsonResponse
167 */
168 public function postEntriesAction(Request $request)
169 {
170 $this->validateAuthentication();
171
172 $url = $request->request->get('url');
173 $title = $request->request->get('title');
174 $isArchived = $request->request->get('archive');
175 $isStarred = $request->request->get('starred');
176
177 $entry = $this->get('wallabag_core.entry_repository')->findByUrlAndUserId($url, $this->getUser()->getId());
178
179 if (false === $entry) {
180 $entry = $this->get('wallabag_core.content_proxy')->updateEntry(
181 new Entry($this->getUser()),
182 $url
183 );
184 }
185
186 if (!is_null($title)) {
187 $entry->setTitle($title);
188 }
189
190 $tags = $request->request->get('tags', '');
191 if (!empty($tags)) {
192 $this->get('wallabag_core.content_proxy')->assignTagsToEntry($entry, $tags);
193 }
194
195 if (!is_null($isStarred)) {
196 $entry->setStarred((bool) $isStarred);
197 }
198
199 if (!is_null($isArchived)) {
200 $entry->setArchived((bool) $isArchived);
201 }
202
203 $em = $this->getDoctrine()->getManager();
204 $em->persist($entry);
205 $em->flush();
206
207 // entry saved, dispatch event about it!
208 $this->get('event_dispatcher')->dispatch(EntrySavedEvent::NAME, new EntrySavedEvent($entry));
209
210 $json = $this->get('serializer')->serialize($entry, 'json');
211
212 return (new JsonResponse())->setJson($json);
213 }
214
215 /**
216 * Change several properties of an entry.
217 *
218 * @ApiDoc(
219 * requirements={
220 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
221 * },
222 * parameters={
223 * {"name"="title", "dataType"="string", "required"=false},
224 * {"name"="tags", "dataType"="string", "required"=false, "format"="tag1,tag2,tag3", "description"="a comma-separated list of tags."},
225 * {"name"="archive", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="archived the entry."},
226 * {"name"="starred", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="starred the entry."},
227 * }
228 * )
229 *
230 * @return JsonResponse
231 */
232 public function patchEntriesAction(Entry $entry, Request $request)
233 {
234 $this->validateAuthentication();
235 $this->validateUserAccess($entry->getUser()->getId());
236
237 $title = $request->request->get('title');
238 $isArchived = $request->request->get('archive');
239 $isStarred = $request->request->get('starred');
240
241 if (!is_null($title)) {
242 $entry->setTitle($title);
243 }
244
245 if (!is_null($isArchived)) {
246 $entry->setArchived((bool) $isArchived);
247 }
248
249 if (!is_null($isStarred)) {
250 $entry->setStarred((bool) $isStarred);
251 }
252
253 $tags = $request->request->get('tags', '');
254 if (!empty($tags)) {
255 $this->get('wallabag_core.content_proxy')->assignTagsToEntry($entry, $tags);
256 }
257
258 $em = $this->getDoctrine()->getManager();
259 $em->flush();
260
261 $json = $this->get('serializer')->serialize($entry, 'json');
262
263 return (new JsonResponse())->setJson($json);
264 }
265
266 /**
267 * Delete **permanently** an entry.
268 *
269 * @ApiDoc(
270 * requirements={
271 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
272 * }
273 * )
274 *
275 * @return JsonResponse
276 */
277 public function deleteEntriesAction(Entry $entry)
278 {
279 $this->validateAuthentication();
280 $this->validateUserAccess($entry->getUser()->getId());
281
282 $em = $this->getDoctrine()->getManager();
283 $em->remove($entry);
284 $em->flush();
285
286 // entry deleted, dispatch event about it!
287 $this->get('event_dispatcher')->dispatch(EntryDeletedEvent::NAME, new EntryDeletedEvent($entry));
288
289 $json = $this->get('serializer')->serialize($entry, 'json');
290
291 return (new JsonResponse())->setJson($json);
292 }
293
294 /**
295 * Retrieve all tags for an entry.
296 *
297 * @ApiDoc(
298 * requirements={
299 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
300 * }
301 * )
302 *
303 * @return JsonResponse
304 */
305 public function getEntriesTagsAction(Entry $entry)
306 {
307 $this->validateAuthentication();
308 $this->validateUserAccess($entry->getUser()->getId());
309
310 $json = $this->get('serializer')->serialize($entry->getTags(), 'json');
311
312 return (new JsonResponse())->setJson($json);
313 }
314
315 /**
316 * Add one or more tags to an entry.
317 *
318 * @ApiDoc(
319 * requirements={
320 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
321 * },
322 * parameters={
323 * {"name"="tags", "dataType"="string", "required"=false, "format"="tag1,tag2,tag3", "description"="a comma-separated list of tags."},
324 * }
325 * )
326 *
327 * @return JsonResponse
328 */
329 public function postEntriesTagsAction(Request $request, Entry $entry)
330 {
331 $this->validateAuthentication();
332 $this->validateUserAccess($entry->getUser()->getId());
333
334 $tags = $request->request->get('tags', '');
335 if (!empty($tags)) {
336 $this->get('wallabag_core.content_proxy')->assignTagsToEntry($entry, $tags);
337 }
338
339 $em = $this->getDoctrine()->getManager();
340 $em->persist($entry);
341 $em->flush();
342
343 $json = $this->get('serializer')->serialize($entry, 'json');
344
345 return (new JsonResponse())->setJson($json);
346 }
347
348 /**
349 * Permanently remove one tag for an entry.
350 *
351 * @ApiDoc(
352 * requirements={
353 * {"name"="tag", "dataType"="integer", "requirement"="\w+", "description"="The tag ID"},
354 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
355 * }
356 * )
357 *
358 * @return JsonResponse
359 */
360 public function deleteEntriesTagsAction(Entry $entry, Tag $tag)
361 {
362 $this->validateAuthentication();
363 $this->validateUserAccess($entry->getUser()->getId());
364
365 $entry->removeTag($tag);
366 $em = $this->getDoctrine()->getManager();
367 $em->persist($entry);
368 $em->flush();
369
370 $json = $this->get('serializer')->serialize($entry, 'json');
371
372 return (new JsonResponse())->setJson($json);
373 }
374}
diff --git a/src/Wallabag/ApiBundle/Controller/TagRestController.php b/src/Wallabag/ApiBundle/Controller/TagRestController.php
new file mode 100644
index 00000000..4e7ddc66
--- /dev/null
+++ b/src/Wallabag/ApiBundle/Controller/TagRestController.php
@@ -0,0 +1,171 @@
1<?php
2
3namespace Wallabag\ApiBundle\Controller;
4
5use Nelmio\ApiDocBundle\Annotation\ApiDoc;
6use Symfony\Component\HttpFoundation\Request;
7use Symfony\Component\HttpFoundation\JsonResponse;
8use Wallabag\CoreBundle\Entity\Entry;
9use Wallabag\CoreBundle\Entity\Tag;
10
11class TagRestController extends WallabagRestController
12{
13 /**
14 * Retrieve all tags.
15 *
16 * @ApiDoc()
17 *
18 * @return JsonResponse
19 */
20 public function getTagsAction()
21 {
22 $this->validateAuthentication();
23
24 $tags = $this->getDoctrine()
25 ->getRepository('WallabagCoreBundle:Tag')
26 ->findAllTags($this->getUser()->getId());
27
28 $json = $this->get('serializer')->serialize($tags, 'json');
29
30 return (new JsonResponse())->setJson($json);
31 }
32
33 /**
34 * Permanently remove one tag from **every** entry.
35 *
36 * @ApiDoc(
37 * requirements={
38 * {"name"="tag", "dataType"="string", "required"=true, "requirement"="\w+", "description"="Tag as a string"}
39 * }
40 * )
41 *
42 * @return JsonResponse
43 */
44 public function deleteTagLabelAction(Request $request)
45 {
46 $this->validateAuthentication();
47 $label = $request->request->get('tag', '');
48
49 $tag = $this->getDoctrine()->getRepository('WallabagCoreBundle:Tag')->findOneByLabel($label);
50
51 if (empty($tag)) {
52 throw $this->createNotFoundException('Tag not found');
53 }
54
55 $this->getDoctrine()
56 ->getRepository('WallabagCoreBundle:Entry')
57 ->removeTag($this->getUser()->getId(), $tag);
58
59 $this->cleanOrphanTag($tag);
60
61 $json = $this->get('serializer')->serialize($tag, 'json');
62
63 return (new JsonResponse())->setJson($json);
64 }
65
66 /**
67 * Permanently remove some tags from **every** entry.
68 *
69 * @ApiDoc(
70 * requirements={
71 * {"name"="tags", "dataType"="string", "required"=true, "format"="tag1,tag2", "description"="Tags as strings (comma splitted)"}
72 * }
73 * )
74 *
75 * @return JsonResponse
76 */
77 public function deleteTagsLabelAction(Request $request)
78 {
79 $this->validateAuthentication();
80
81 $tagsLabels = $request->request->get('tags', '');
82
83 $tags = [];
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
93 if (empty($tags)) {
94 throw $this->createNotFoundException('Tags not found');
95 }
96
97 $this->getDoctrine()
98 ->getRepository('WallabagCoreBundle:Entry')
99 ->removeTags($this->getUser()->getId(), $tags);
100
101 $this->cleanOrphanTag($tags);
102
103 $json = $this->get('serializer')->serialize($tags, 'json');
104
105 return (new JsonResponse())->setJson($json);
106 }
107
108 /**
109 * Permanently remove one tag from **every** entry.
110 *
111 * @ApiDoc(
112 * requirements={
113 * {"name"="tag", "dataType"="integer", "requirement"="\w+", "description"="The tag"}
114 * }
115 * )
116 *
117 * @return JsonResponse
118 */
119 public function deleteTagAction(Tag $tag)
120 {
121 $this->validateAuthentication();
122
123 $this->getDoctrine()
124 ->getRepository('WallabagCoreBundle:Entry')
125 ->removeTag($this->getUser()->getId(), $tag);
126
127 $this->cleanOrphanTag($tag);
128
129 $json = $this->get('serializer')->serialize($tag, 'json');
130
131 return (new JsonResponse())->setJson($json);
132 }
133
134 /**
135 * Retrieve version number.
136 *
137 * @ApiDoc()
138 *
139 * @return JsonResponse
140 */
141 public function getVersionAction()
142 {
143 $version = $this->container->getParameter('wallabag_core.version');
144
145 $json = $this->get('serializer')->serialize($version, 'json');
146
147 return (new JsonResponse())->setJson($json);
148 }
149
150 /**
151 * Remove orphan tag in case no entries are associated to it.
152 *
153 * @param Tag|array $tags
154 */
155 private function cleanOrphanTag($tags)
156 {
157 if (!is_array($tags)) {
158 $tags = [$tags];
159 }
160
161 $em = $this->getDoctrine()->getManager();
162
163 foreach ($tags as $tag) {
164 if (count($tag->getEntries()) === 0) {
165 $em->remove($tag);
166 }
167 }
168
169 $em->flush();
170 }
171}
diff --git a/src/Wallabag/ApiBundle/Controller/WallabagRestController.php b/src/Wallabag/ApiBundle/Controller/WallabagRestController.php
index 50652b77..544c1ea9 100644
--- a/src/Wallabag/ApiBundle/Controller/WallabagRestController.php
+++ b/src/Wallabag/ApiBundle/Controller/WallabagRestController.php
@@ -3,627 +3,11 @@
3namespace Wallabag\ApiBundle\Controller; 3namespace Wallabag\ApiBundle\Controller;
4 4
5use FOS\RestBundle\Controller\FOSRestController; 5use FOS\RestBundle\Controller\FOSRestController;
6use Hateoas\Configuration\Route as HateoasRoute;
7use Hateoas\Representation\Factory\PagerfantaFactory;
8use Nelmio\ApiDocBundle\Annotation\ApiDoc;
9use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
10use Symfony\Component\HttpFoundation\Request;
11use Symfony\Component\HttpFoundation\JsonResponse;
12use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
13use Symfony\Component\Security\Core\Exception\AccessDeniedException; 6use Symfony\Component\Security\Core\Exception\AccessDeniedException;
14use Wallabag\CoreBundle\Entity\Entry; 7use Wallabag\CoreBundle\Entity\Entry;
15use Wallabag\CoreBundle\Entity\Tag;
16use Wallabag\AnnotationBundle\Entity\Annotation;
17use Wallabag\CoreBundle\Event\EntrySavedEvent;
18use Wallabag\CoreBundle\Event\EntryDeletedEvent;
19 8
20class WallabagRestController extends FOSRestController 9class WallabagRestController extends FOSRestController
21{ 10{
22 private function validateAuthentication()
23 {
24 if (false === $this->get('security.authorization_checker')->isGranted('IS_AUTHENTICATED_FULLY')) {
25 throw new AccessDeniedException();
26 }
27 }
28
29 /**
30 * Check if an entry exist by url.
31 *
32 * @ApiDoc(
33 * parameters={
34 * {"name"="url", "dataType"="string", "required"=true, "format"="An url", "description"="Url to check if it exists"},
35 * {"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"}
36 * }
37 * )
38 *
39 * @return JsonResponse
40 */
41 public function getEntriesExistsAction(Request $request)
42 {
43 $this->validateAuthentication();
44
45 $urls = $request->query->get('urls', []);
46
47 // handle multiple urls first
48 if (!empty($urls)) {
49 $results = [];
50 foreach ($urls as $url) {
51 $res = $this->getDoctrine()
52 ->getRepository('WallabagCoreBundle:Entry')
53 ->findByUrlAndUserId($url, $this->getUser()->getId());
54
55 $results[$url] = false === $res ? false : true;
56 }
57
58 $json = $this->get('serializer')->serialize($results, 'json');
59
60 return (new JsonResponse())->setJson($json);
61 }
62
63 // let's see if it is a simple url?
64 $url = $request->query->get('url', '');
65
66 if (empty($url)) {
67 throw $this->createAccessDeniedException('URL is empty?, logged user id: '.$this->getUser()->getId());
68 }
69
70 $res = $this->getDoctrine()
71 ->getRepository('WallabagCoreBundle:Entry')
72 ->findByUrlAndUserId($url, $this->getUser()->getId());
73
74 $exists = false === $res ? false : true;
75
76 $json = $this->get('serializer')->serialize(['exists' => $exists], 'json');
77
78 return (new JsonResponse())->setJson($json);
79 }
80
81 /**
82 * Retrieve all entries. It could be filtered by many options.
83 *
84 * @ApiDoc(
85 * parameters={
86 * {"name"="archive", "dataType"="integer", "required"=false, "format"="1 or 0, all entries by default", "description"="filter by archived status."},
87 * {"name"="starred", "dataType"="integer", "required"=false, "format"="1 or 0, all entries by default", "description"="filter by starred status."},
88 * {"name"="sort", "dataType"="string", "required"=false, "format"="'created' or 'updated', default 'created'", "description"="sort entries by date."},
89 * {"name"="order", "dataType"="string", "required"=false, "format"="'asc' or 'desc', default 'desc'", "description"="order of sort."},
90 * {"name"="page", "dataType"="integer", "required"=false, "format"="default '1'", "description"="what page you want."},
91 * {"name"="perPage", "dataType"="integer", "required"=false, "format"="default'30'", "description"="results per page."},
92 * {"name"="tags", "dataType"="string", "required"=false, "format"="api,rest", "description"="a list of tags url encoded. Will returns entries that matches ALL tags."},
93 * {"name"="since", "dataType"="integer", "required"=false, "format"="default '0'", "description"="The timestamp since when you want entries updated."},
94 * }
95 * )
96 *
97 * @return JsonResponse
98 */
99 public function getEntriesAction(Request $request)
100 {
101 $this->validateAuthentication();
102
103 $isArchived = (null === $request->query->get('archive')) ? null : (bool) $request->query->get('archive');
104 $isStarred = (null === $request->query->get('starred')) ? null : (bool) $request->query->get('starred');
105 $sort = $request->query->get('sort', 'created');
106 $order = $request->query->get('order', 'desc');
107 $page = (int) $request->query->get('page', 1);
108 $perPage = (int) $request->query->get('perPage', 30);
109 $tags = $request->query->get('tags', '');
110 $since = $request->query->get('since', 0);
111
112 $pager = $this->getDoctrine()
113 ->getRepository('WallabagCoreBundle:Entry')
114 ->findEntries($this->getUser()->getId(), $isArchived, $isStarred, $sort, $order, $since, $tags);
115
116 $pager->setCurrentPage($page);
117 $pager->setMaxPerPage($perPage);
118
119 $pagerfantaFactory = new PagerfantaFactory('page', 'perPage');
120 $paginatedCollection = $pagerfantaFactory->createRepresentation(
121 $pager,
122 new HateoasRoute(
123 'api_get_entries',
124 [
125 'archive' => $isArchived,
126 'starred' => $isStarred,
127 'sort' => $sort,
128 'order' => $order,
129 'page' => $page,
130 'perPage' => $perPage,
131 'tags' => $tags,
132 'since' => $since,
133 ],
134 UrlGeneratorInterface::ABSOLUTE_URL
135 )
136 );
137
138 $json = $this->get('serializer')->serialize($paginatedCollection, 'json');
139
140 return (new JsonResponse())->setJson($json);
141 }
142
143 /**
144 * Retrieve a single entry.
145 *
146 * @ApiDoc(
147 * requirements={
148 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
149 * }
150 * )
151 *
152 * @return JsonResponse
153 */
154 public function getEntryAction(Entry $entry)
155 {
156 $this->validateAuthentication();
157 $this->validateUserAccess($entry->getUser()->getId());
158
159 $json = $this->get('serializer')->serialize($entry, 'json');
160
161 return (new JsonResponse())->setJson($json);
162 }
163
164 /**
165 * Retrieve a single entry as a predefined format.
166 *
167 * @ApiDoc(
168 * requirements={
169 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
170 * }
171 * )
172 *
173 * @return Response
174 */
175 public function getEntryExportAction(Entry $entry, Request $request)
176 {
177 $this->validateAuthentication();
178 $this->validateUserAccess($entry->getUser()->getId());
179
180 return $this->get('wallabag_core.helper.entries_export')
181 ->setEntries($entry)
182 ->updateTitle('entry')
183 ->exportAs($request->attributes->get('_format'));
184 }
185
186 /**
187 * Create an entry.
188 *
189 * @ApiDoc(
190 * parameters={
191 * {"name"="url", "dataType"="string", "required"=true, "format"="http://www.test.com/article.html", "description"="Url for the entry."},
192 * {"name"="title", "dataType"="string", "required"=false, "description"="Optional, we'll get the title from the page."},
193 * {"name"="tags", "dataType"="string", "required"=false, "format"="tag1,tag2,tag3", "description"="a comma-separated list of tags."},
194 * {"name"="starred", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="entry already starred"},
195 * {"name"="archive", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="entry already archived"},
196 * }
197 * )
198 *
199 * @return JsonResponse
200 */
201 public function postEntriesAction(Request $request)
202 {
203 $this->validateAuthentication();
204
205 $url = $request->request->get('url');
206 $title = $request->request->get('title');
207 $isArchived = $request->request->get('archive');
208 $isStarred = $request->request->get('starred');
209
210 $entry = $this->get('wallabag_core.entry_repository')->findByUrlAndUserId($url, $this->getUser()->getId());
211
212 if (false === $entry) {
213 $entry = $this->get('wallabag_core.content_proxy')->updateEntry(
214 new Entry($this->getUser()),
215 $url
216 );
217 }
218
219 if (!is_null($title)) {
220 $entry->setTitle($title);
221 }
222
223 $tags = $request->request->get('tags', '');
224 if (!empty($tags)) {
225 $this->get('wallabag_core.content_proxy')->assignTagsToEntry($entry, $tags);
226 }
227
228 if (!is_null($isStarred)) {
229 $entry->setStarred((bool) $isStarred);
230 }
231
232 if (!is_null($isArchived)) {
233 $entry->setArchived((bool) $isArchived);
234 }
235
236 $em = $this->getDoctrine()->getManager();
237 $em->persist($entry);
238 $em->flush();
239
240 // entry saved, dispatch event about it!
241 $this->get('event_dispatcher')->dispatch(EntrySavedEvent::NAME, new EntrySavedEvent($entry));
242
243 $json = $this->get('serializer')->serialize($entry, 'json');
244
245 return (new JsonResponse())->setJson($json);
246 }
247
248 /**
249 * Change several properties of an entry.
250 *
251 * @ApiDoc(
252 * requirements={
253 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
254 * },
255 * parameters={
256 * {"name"="title", "dataType"="string", "required"=false},
257 * {"name"="tags", "dataType"="string", "required"=false, "format"="tag1,tag2,tag3", "description"="a comma-separated list of tags."},
258 * {"name"="archive", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="archived the entry."},
259 * {"name"="starred", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="starred the entry."},
260 * }
261 * )
262 *
263 * @return JsonResponse
264 */
265 public function patchEntriesAction(Entry $entry, Request $request)
266 {
267 $this->validateAuthentication();
268 $this->validateUserAccess($entry->getUser()->getId());
269
270 $title = $request->request->get('title');
271 $isArchived = $request->request->get('archive');
272 $isStarred = $request->request->get('starred');
273
274 if (!is_null($title)) {
275 $entry->setTitle($title);
276 }
277
278 if (!is_null($isArchived)) {
279 $entry->setArchived((bool) $isArchived);
280 }
281
282 if (!is_null($isStarred)) {
283 $entry->setStarred((bool) $isStarred);
284 }
285
286 $tags = $request->request->get('tags', '');
287 if (!empty($tags)) {
288 $this->get('wallabag_core.content_proxy')->assignTagsToEntry($entry, $tags);
289 }
290
291 $em = $this->getDoctrine()->getManager();
292 $em->flush();
293
294 $json = $this->get('serializer')->serialize($entry, 'json');
295
296 return (new JsonResponse())->setJson($json);
297 }
298
299 /**
300 * Delete **permanently** an entry.
301 *
302 * @ApiDoc(
303 * requirements={
304 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
305 * }
306 * )
307 *
308 * @return JsonResponse
309 */
310 public function deleteEntriesAction(Entry $entry)
311 {
312 $this->validateAuthentication();
313 $this->validateUserAccess($entry->getUser()->getId());
314
315 // entry deleted, dispatch event about it!
316 $this->get('event_dispatcher')->dispatch(EntryDeletedEvent::NAME, new EntryDeletedEvent($entry));
317
318 $em = $this->getDoctrine()->getManager();
319 $em->remove($entry);
320 $em->flush();
321
322 $json = $this->get('serializer')->serialize($entry, 'json');
323
324 return (new JsonResponse())->setJson($json);
325 }
326
327 /**
328 * Retrieve all tags for an entry.
329 *
330 * @ApiDoc(
331 * requirements={
332 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
333 * }
334 * )
335 *
336 * @return JsonResponse
337 */
338 public function getEntriesTagsAction(Entry $entry)
339 {
340 $this->validateAuthentication();
341 $this->validateUserAccess($entry->getUser()->getId());
342
343 $json = $this->get('serializer')->serialize($entry->getTags(), 'json');
344
345 return (new JsonResponse())->setJson($json);
346 }
347
348 /**
349 * Add one or more tags to an entry.
350 *
351 * @ApiDoc(
352 * requirements={
353 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
354 * },
355 * parameters={
356 * {"name"="tags", "dataType"="string", "required"=false, "format"="tag1,tag2,tag3", "description"="a comma-separated list of tags."},
357 * }
358 * )
359 *
360 * @return JsonResponse
361 */
362 public function postEntriesTagsAction(Request $request, Entry $entry)
363 {
364 $this->validateAuthentication();
365 $this->validateUserAccess($entry->getUser()->getId());
366
367 $tags = $request->request->get('tags', '');
368 if (!empty($tags)) {
369 $this->get('wallabag_core.content_proxy')->assignTagsToEntry($entry, $tags);
370 }
371
372 $em = $this->getDoctrine()->getManager();
373 $em->persist($entry);
374 $em->flush();
375
376 $json = $this->get('serializer')->serialize($entry, 'json');
377
378 return (new JsonResponse())->setJson($json);
379 }
380
381 /**
382 * Permanently remove one tag for an entry.
383 *
384 * @ApiDoc(
385 * requirements={
386 * {"name"="tag", "dataType"="integer", "requirement"="\w+", "description"="The tag ID"},
387 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
388 * }
389 * )
390 *
391 * @return JsonResponse
392 */
393 public function deleteEntriesTagsAction(Entry $entry, Tag $tag)
394 {
395 $this->validateAuthentication();
396 $this->validateUserAccess($entry->getUser()->getId());
397
398 $entry->removeTag($tag);
399 $em = $this->getDoctrine()->getManager();
400 $em->persist($entry);
401 $em->flush();
402
403 $json = $this->get('serializer')->serialize($entry, 'json');
404
405 return (new JsonResponse())->setJson($json);
406 }
407
408 /**
409 * Retrieve all tags.
410 *
411 * @ApiDoc()
412 *
413 * @return JsonResponse
414 */
415 public function getTagsAction()
416 {
417 $this->validateAuthentication();
418
419 $tags = $this->getDoctrine()
420 ->getRepository('WallabagCoreBundle:Tag')
421 ->findAllTags($this->getUser()->getId());
422
423 $json = $this->get('serializer')->serialize($tags, 'json');
424
425 return (new JsonResponse())->setJson($json);
426 }
427
428 /**
429 * Permanently remove one tag from **every** entry.
430 *
431 * @ApiDoc(
432 * requirements={
433 * {"name"="tag", "dataType"="string", "required"=true, "requirement"="\w+", "description"="Tag as a string"}
434 * }
435 * )
436 *
437 * @return JsonResponse
438 */
439 public function deleteTagLabelAction(Request $request)
440 {
441 $this->validateAuthentication();
442 $label = $request->request->get('tag', '');
443
444 $tag = $this->getDoctrine()->getRepository('WallabagCoreBundle:Tag')->findOneByLabel($label);
445
446 if (empty($tag)) {
447 throw $this->createNotFoundException('Tag not found');
448 }
449
450 $this->getDoctrine()
451 ->getRepository('WallabagCoreBundle:Entry')
452 ->removeTag($this->getUser()->getId(), $tag);
453
454 $this->cleanOrphanTag($tag);
455
456 $json = $this->get('serializer')->serialize($tag, 'json');
457
458 return (new JsonResponse())->setJson($json);
459 }
460
461 /**
462 * Permanently remove some tags from **every** entry.
463 *
464 * @ApiDoc(
465 * requirements={
466 * {"name"="tags", "dataType"="string", "required"=true, "format"="tag1,tag2", "description"="Tags as strings (comma splitted)"}
467 * }
468 * )
469 *
470 * @return JsonResponse
471 */
472 public function deleteTagsLabelAction(Request $request)
473 {
474 $this->validateAuthentication();
475
476 $tagsLabels = $request->request->get('tags', '');
477
478 $tags = [];
479
480 foreach (explode(',', $tagsLabels) as $tagLabel) {
481 $tagEntity = $this->getDoctrine()->getRepository('WallabagCoreBundle:Tag')->findOneByLabel($tagLabel);
482
483 if (!empty($tagEntity)) {
484 $tags[] = $tagEntity;
485 }
486 }
487
488 if (empty($tags)) {
489 throw $this->createNotFoundException('Tags not found');
490 }
491
492 $this->getDoctrine()
493 ->getRepository('WallabagCoreBundle:Entry')
494 ->removeTags($this->getUser()->getId(), $tags);
495
496 $this->cleanOrphanTag($tags);
497
498 $json = $this->get('serializer')->serialize($tags, 'json');
499
500 return (new JsonResponse())->setJson($json);
501 }
502
503 /**
504 * Permanently remove one tag from **every** entry.
505 *
506 * @ApiDoc(
507 * requirements={
508 * {"name"="tag", "dataType"="integer", "requirement"="\w+", "description"="The tag"}
509 * }
510 * )
511 *
512 * @return JsonResponse
513 */
514 public function deleteTagAction(Tag $tag)
515 {
516 $this->validateAuthentication();
517
518 $this->getDoctrine()
519 ->getRepository('WallabagCoreBundle:Entry')
520 ->removeTag($this->getUser()->getId(), $tag);
521
522 $this->cleanOrphanTag($tag);
523
524 $json = $this->get('serializer')->serialize($tag, 'json');
525
526 return (new JsonResponse())->setJson($json);
527 }
528
529 /**
530 * Retrieve annotations for an entry.
531 *
532 * @ApiDoc(
533 * requirements={
534 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
535 * }
536 * )
537 *
538 * @param Entry $entry
539 *
540 * @return JsonResponse
541 */
542 public function getAnnotationsAction(Entry $entry)
543 {
544 $this->validateAuthentication();
545
546 return $this->forward('WallabagAnnotationBundle:WallabagAnnotation:getAnnotations', [
547 'entry' => $entry,
548 ]);
549 }
550
551 /**
552 * Creates a new annotation.
553 *
554 * @ApiDoc(
555 * requirements={
556 * {"name"="ranges", "dataType"="array", "requirement"="\w+", "description"="The range array for the annotation"},
557 * {"name"="quote", "dataType"="string", "required"=false, "description"="Optional, quote for the annotation"},
558 * {"name"="text", "dataType"="string", "required"=true, "description"=""},
559 * }
560 * )
561 *
562 * @param Request $request
563 * @param Entry $entry
564 *
565 * @return JsonResponse
566 */
567 public function postAnnotationAction(Request $request, Entry $entry)
568 {
569 $this->validateAuthentication();
570
571 return $this->forward('WallabagAnnotationBundle:WallabagAnnotation:postAnnotation', [
572 'request' => $request,
573 'entry' => $entry,
574 ]);
575 }
576
577 /**
578 * Updates an annotation.
579 *
580 * @ApiDoc(
581 * requirements={
582 * {"name"="annotation", "dataType"="string", "requirement"="\w+", "description"="The annotation ID"}
583 * }
584 * )
585 *
586 * @ParamConverter("annotation", class="WallabagAnnotationBundle:Annotation")
587 *
588 * @param Annotation $annotation
589 * @param Request $request
590 *
591 * @return JsonResponse
592 */
593 public function putAnnotationAction(Annotation $annotation, Request $request)
594 {
595 $this->validateAuthentication();
596
597 return $this->forward('WallabagAnnotationBundle:WallabagAnnotation:putAnnotation', [
598 'annotation' => $annotation,
599 'request' => $request,
600 ]);
601 }
602
603 /**
604 * Removes an annotation.
605 *
606 * @ApiDoc(
607 * requirements={
608 * {"name"="annotation", "dataType"="string", "requirement"="\w+", "description"="The annotation ID"}
609 * }
610 * )
611 *
612 * @ParamConverter("annotation", class="WallabagAnnotationBundle:Annotation")
613 *
614 * @param Annotation $annotation
615 *
616 * @return JsonResponse
617 */
618 public function deleteAnnotationAction(Annotation $annotation)
619 {
620 $this->validateAuthentication();
621
622 return $this->forward('WallabagAnnotationBundle:WallabagAnnotation:deleteAnnotation', [
623 'annotation' => $annotation,
624 ]);
625 }
626
627 /** 11 /**
628 * Retrieve version number. 12 * Retrieve version number.
629 * 13 *
@@ -634,32 +18,15 @@ class WallabagRestController extends FOSRestController
634 public function getVersionAction() 18 public function getVersionAction()
635 { 19 {
636 $version = $this->container->getParameter('wallabag_core.version'); 20 $version = $this->container->getParameter('wallabag_core.version');
637
638 $json = $this->get('serializer')->serialize($version, 'json'); 21 $json = $this->get('serializer')->serialize($version, 'json');
639
640 return (new JsonResponse())->setJson($json); 22 return (new JsonResponse())->setJson($json);
641 } 23 }
642 24
643 /** 25 protected function validateAuthentication()
644 * Remove orphan tag in case no entries are associated to it.
645 *
646 * @param Tag|array $tags
647 */
648 private function cleanOrphanTag($tags)
649 { 26 {
650 if (!is_array($tags)) { 27 if (false === $this->get('security.authorization_checker')->isGranted('IS_AUTHENTICATED_FULLY')) {
651 $tags = [$tags]; 28 throw new AccessDeniedException();
652 }
653
654 $em = $this->getDoctrine()->getManager();
655
656 foreach ($tags as $tag) {
657 if (count($tag->getEntries()) === 0) {
658 $em->remove($tag);
659 }
660 } 29 }
661
662 $em->flush();
663 } 30 }
664 31
665 /** 32 /**
@@ -668,7 +35,7 @@ class WallabagRestController extends FOSRestController
668 * 35 *
669 * @param int $requestUserId User id from the requested source 36 * @param int $requestUserId User id from the requested source
670 */ 37 */
671 private function validateUserAccess($requestUserId) 38 protected function validateUserAccess($requestUserId)
672 { 39 {
673 $user = $this->get('security.token_storage')->getToken()->getUser(); 40 $user = $this->get('security.token_storage')->getToken()->getUser();
674 if ($requestUserId != $user->getId()) { 41 if ($requestUserId != $user->getId()) {
diff --git a/src/Wallabag/ApiBundle/Resources/config/routing_rest.yml b/src/Wallabag/ApiBundle/Resources/config/routing_rest.yml
index 35f8b2c1..8e1886ac 100644
--- a/src/Wallabag/ApiBundle/Resources/config/routing_rest.yml
+++ b/src/Wallabag/ApiBundle/Resources/config/routing_rest.yml
@@ -1,4 +1,14 @@
1api: 1entry:
2 type: rest 2 type: rest
3 resource: "WallabagApiBundle:WallabagRest" 3 resource: "WallabagApiBundle:EntryRest"
4 name_prefix: api_ 4 name_prefix: api_
5
6tag:
7 type: rest
8 resource: "WallabagApiBundle:TagRest"
9 name_prefix: api_
10
11misc:
12 type: rest
13 resource: "WallabagApiBundle:WallabagRest"
14 name_prefix: api_
diff --git a/src/Wallabag/CoreBundle/Controller/ExportController.php b/src/Wallabag/CoreBundle/Controller/ExportController.php
index 6191d5d7..79653cfe 100644
--- a/src/Wallabag/CoreBundle/Controller/ExportController.php
+++ b/src/Wallabag/CoreBundle/Controller/ExportController.php
@@ -4,8 +4,10 @@ namespace Wallabag\CoreBundle\Controller;
4 4
5use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; 5use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
6use Symfony\Bundle\FrameworkBundle\Controller\Controller; 6use Symfony\Bundle\FrameworkBundle\Controller\Controller;
7use Symfony\Component\HttpFoundation\Request;
7use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; 8use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
8use Wallabag\CoreBundle\Entity\Entry; 9use Wallabag\CoreBundle\Entity\Entry;
10use Wallabag\CoreBundle\Entity\Tag;
9 11
10/** 12/**
11 * The try/catch can be removed once all formats will be implemented. 13 * The try/catch can be removed once all formats will be implemented.
@@ -51,15 +53,24 @@ class ExportController extends Controller
51 * 53 *
52 * @return \Symfony\Component\HttpFoundation\Response 54 * @return \Symfony\Component\HttpFoundation\Response
53 */ 55 */
54 public function downloadEntriesAction($format, $category) 56 public function downloadEntriesAction(Request $request, $format, $category)
55 { 57 {
56 $method = ucfirst($category); 58 $method = ucfirst($category);
57 $methodBuilder = 'getBuilderFor'.$method.'ByUser'; 59 $methodBuilder = 'getBuilderFor'.$method.'ByUser';
58 $entries = $this->getDoctrine() 60
59 ->getRepository('WallabagCoreBundle:Entry') 61 if ($category == 'tag_entries') {
60 ->$methodBuilder($this->getUser()->getId()) 62 $tag = $this->getDoctrine()->getRepository('WallabagCoreBundle:Tag')->findOneBySlug($request->query->get('tag'));
61 ->getQuery() 63
62 ->getResult(); 64 $entries = $this->getDoctrine()
65 ->getRepository('WallabagCoreBundle:Entry')
66 ->findAllByTagId($this->getUser()->getId(), $tag->getId());
67 } else {
68 $entries = $this->getDoctrine()
69 ->getRepository('WallabagCoreBundle:Entry')
70 ->$methodBuilder($this->getUser()->getId())
71 ->getQuery()
72 ->getResult();
73 }
63 74
64 try { 75 try {
65 return $this->get('wallabag_core.helper.entries_export') 76 return $this->get('wallabag_core.helper.entries_export')
diff --git a/src/Wallabag/CoreBundle/Controller/TagController.php b/src/Wallabag/CoreBundle/Controller/TagController.php
index 4542d484..a3e70fd0 100644
--- a/src/Wallabag/CoreBundle/Controller/TagController.php
+++ b/src/Wallabag/CoreBundle/Controller/TagController.php
@@ -143,6 +143,7 @@ class TagController extends Controller
143 'form' => null, 143 'form' => null,
144 'entries' => $entries, 144 'entries' => $entries,
145 'currentPage' => $page, 145 'currentPage' => $page,
146 'tag' => $tag->getLabel(),
146 ]); 147 ]);
147 } 148 }
148} 149}
diff --git a/src/Wallabag/CoreBundle/Helper/EntriesExport.php b/src/Wallabag/CoreBundle/Helper/EntriesExport.php
index e50c68a6..4bf292a4 100644
--- a/src/Wallabag/CoreBundle/Helper/EntriesExport.php
+++ b/src/Wallabag/CoreBundle/Helper/EntriesExport.php
@@ -8,7 +8,6 @@ use JMS\Serializer\SerializerBuilder;
8use PHPePub\Core\EPub; 8use PHPePub\Core\EPub;
9use PHPePub\Core\Structure\OPF\DublinCore; 9use PHPePub\Core\Structure\OPF\DublinCore;
10use Symfony\Component\HttpFoundation\Response; 10use Symfony\Component\HttpFoundation\Response;
11use Craue\ConfigBundle\Util\Config;
12 11
13/** 12/**
14 * This class doesn't have unit test BUT it's fully covered by a functional test with ExportControllerTest. 13 * This class doesn't have unit test BUT it's fully covered by a functional test with ExportControllerTest.
@@ -27,12 +26,12 @@ class EntriesExport
27 </div>'; 26 </div>';
28 27
29 /** 28 /**
30 * @param Config $craueConfig CraueConfig instance to get wallabag instance url from database 29 * @param string $wallabagUrl Wallabag instance url
31 * @param string $logoPath Path to the logo FROM THE BUNDLE SCOPE 30 * @param string $logoPath Path to the logo FROM THE BUNDLE SCOPE
32 */ 31 */
33 public function __construct(Config $craueConfig, $logoPath) 32 public function __construct($wallabagUrl, $logoPath)
34 { 33 {
35 $this->wallabagUrl = $craueConfig->get('wallabag_url'); 34 $this->wallabagUrl = $wallabagUrl;
36 $this->logoPath = $logoPath; 35 $this->logoPath = $logoPath;
37 } 36 }
38 37
diff --git a/src/Wallabag/CoreBundle/Resources/config/services.yml b/src/Wallabag/CoreBundle/Resources/config/services.yml
index 56d776ad..9786ac27 100644
--- a/src/Wallabag/CoreBundle/Resources/config/services.yml
+++ b/src/Wallabag/CoreBundle/Resources/config/services.yml
@@ -55,6 +55,7 @@ services:
55 '.fok.nl': 'Googlebot/2.1' 55 '.fok.nl': 'Googlebot/2.1'
56 'getpocket.com': 'PHP/5.2' 56 'getpocket.com': 'PHP/5.2'
57 'iansommerville.com': 'PHP/5.2' 57 'iansommerville.com': 'PHP/5.2'
58 '.slashdot.org': 'PHP/5.2'
58 calls: 59 calls:
59 - [ setLogger, [ "@logger" ] ] 60 - [ setLogger, [ "@logger" ] ]
60 tags: 61 tags:
@@ -91,7 +92,7 @@ services:
91 wallabag_core.helper.entries_export: 92 wallabag_core.helper.entries_export:
92 class: Wallabag\CoreBundle\Helper\EntriesExport 93 class: Wallabag\CoreBundle\Helper\EntriesExport
93 arguments: 94 arguments:
94 - "@craue_config" 95 - '@=service(''craue_config'').get(''wallabag_url'')'
95 - src/Wallabag/CoreBundle/Resources/public/themes/_global/img/appicon/apple-touch-icon-152.png 96 - src/Wallabag/CoreBundle/Resources/public/themes/_global/img/appicon/apple-touch-icon-152.png
96 97
97 wallabag.operator.array.matches: 98 wallabag.operator.array.matches:
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.da.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.da.yml
index 21c26079..aeae6bcf 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.da.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.da.yml
@@ -150,7 +150,7 @@ entry:
150 # starred: 'Starred entries' 150 # starred: 'Starred entries'
151 # archived: 'Archived entries' 151 # archived: 'Archived entries'
152 # filtered: 'Filtered entries' 152 # filtered: 'Filtered entries'
153 # filtered_tags: 'Filtered by tags' 153 # filtered_tags: 'Filtered by tags:'
154 # untagged: 'Untagged entries' 154 # untagged: 'Untagged entries'
155 list: 155 list:
156 # number_on_the_page: '{0} There is no entry.|{1} There is one entry.|]1,Inf[ There are %count% entries.' 156 # number_on_the_page: '{0} There is no entry.|{1} There is one entry.|]1,Inf[ There are %count% entries.'
@@ -472,7 +472,6 @@ flashes:
472 rss_updated: 'RSS-oplysninger opdateret' 472 rss_updated: 'RSS-oplysninger opdateret'
473 # tagging_rules_updated: 'Tagging rules updated' 473 # tagging_rules_updated: 'Tagging rules updated'
474 # tagging_rules_deleted: 'Tagging rule deleted' 474 # tagging_rules_deleted: 'Tagging rule deleted'
475 # user_added: 'User "%username%" added'
476 # rss_token_updated: 'RSS token updated' 475 # rss_token_updated: 'RSS token updated'
477 # annotations_reset: Annotations reset 476 # annotations_reset: Annotations reset
478 # tags_reset: Tags reset 477 # tags_reset: Tags reset
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.de.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.de.yml
index ff70cbee..2105d02d 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.de.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.de.yml
@@ -150,7 +150,7 @@ entry:
150 starred: 'Favorisierte Einträge' 150 starred: 'Favorisierte Einträge'
151 archived: 'Archivierte Einträge' 151 archived: 'Archivierte Einträge'
152 filtered: 'Gefilterte Einträge' 152 filtered: 'Gefilterte Einträge'
153 filtered_tags: 'Gefiltert nach Tags' 153 filtered_tags: 'Gefiltert nach Tags:'
154 untagged: 'Nicht getaggte Einträge' 154 untagged: 'Nicht getaggte Einträge'
155 list: 155 list:
156 number_on_the_page: '{0} Es gibt keine Einträge.|{1} Es gibt einen Eintrag.|]1,Inf[ Es gibt %count% Einträge.' 156 number_on_the_page: '{0} Es gibt keine Einträge.|{1} Es gibt einen Eintrag.|]1,Inf[ Es gibt %count% Einträge.'
@@ -472,7 +472,6 @@ flashes:
472 rss_updated: 'RSS-Informationen aktualisiert' 472 rss_updated: 'RSS-Informationen aktualisiert'
473 tagging_rules_updated: 'Tagging-Regeln aktualisiert' 473 tagging_rules_updated: 'Tagging-Regeln aktualisiert'
474 tagging_rules_deleted: 'Tagging-Regel gelöscht' 474 tagging_rules_deleted: 'Tagging-Regel gelöscht'
475 user_added: 'Benutzer "%username%" erstellt'
476 rss_token_updated: 'RSS-Token aktualisiert' 475 rss_token_updated: 'RSS-Token aktualisiert'
477 # annotations_reset: Annotations reset 476 # annotations_reset: Annotations reset
478 # tags_reset: Tags reset 477 # tags_reset: Tags reset
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.en.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.en.yml
index 36382b6f..2bb95728 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.en.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.en.yml
@@ -150,7 +150,7 @@ entry:
150 starred: 'Starred entries' 150 starred: 'Starred entries'
151 archived: 'Archived entries' 151 archived: 'Archived entries'
152 filtered: 'Filtered entries' 152 filtered: 'Filtered entries'
153 filtered_tags: 'Filtered by tags' 153 filtered_tags: 'Filtered by tags:'
154 untagged: 'Untagged entries' 154 untagged: 'Untagged entries'
155 list: 155 list:
156 number_on_the_page: '{0} There are no entries.|{1} There is one entry.|]1,Inf[ There are %count% entries.' 156 number_on_the_page: '{0} There are no entries.|{1} There is one entry.|]1,Inf[ There are %count% entries.'
@@ -472,7 +472,6 @@ flashes:
472 rss_updated: 'RSS information updated' 472 rss_updated: 'RSS information updated'
473 tagging_rules_updated: 'Tagging rules updated' 473 tagging_rules_updated: 'Tagging rules updated'
474 tagging_rules_deleted: 'Tagging rule deleted' 474 tagging_rules_deleted: 'Tagging rule deleted'
475 # user_added: 'User "%username%" added'
476 rss_token_updated: 'RSS token updated' 475 rss_token_updated: 'RSS token updated'
477 annotations_reset: Annotations reset 476 annotations_reset: Annotations reset
478 tags_reset: Tags reset 477 tags_reset: Tags reset
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.es.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.es.yml
index 2c80fe8f..ca3db487 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.es.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.es.yml
@@ -150,7 +150,7 @@ entry:
150 starred: 'Artículos favoritos' 150 starred: 'Artículos favoritos'
151 archived: 'Artículos archivados' 151 archived: 'Artículos archivados'
152 filtered: 'Artículos filtrados' 152 filtered: 'Artículos filtrados'
153 # filtered_tags: 'Filtered by tags' 153 # filtered_tags: 'Filtered by tags:'
154 # untagged: 'Untagged entries' 154 # untagged: 'Untagged entries'
155 list: 155 list:
156 number_on_the_page: '{0} No hay artículos.|{1} Hay un artículo.|]1,Inf[ Hay %count% artículos.' 156 number_on_the_page: '{0} No hay artículos.|{1} Hay un artículo.|]1,Inf[ Hay %count% artículos.'
@@ -472,7 +472,6 @@ flashes:
472 rss_updated: 'La configuración de los feeds RSS ha sido actualizada' 472 rss_updated: 'La configuración de los feeds RSS ha sido actualizada'
473 tagging_rules_updated: 'Regla de etiquetado borrada' 473 tagging_rules_updated: 'Regla de etiquetado borrada'
474 tagging_rules_deleted: 'Regla de etiquetado actualizada' 474 tagging_rules_deleted: 'Regla de etiquetado actualizada'
475 user_added: 'Usuario "%username%" añadido'
476 rss_token_updated: 'RSS token actualizado' 475 rss_token_updated: 'RSS token actualizado'
477 # annotations_reset: Annotations reset 476 # annotations_reset: Annotations reset
478 # tags_reset: Tags reset 477 # tags_reset: Tags reset
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml
index 6b6211d6..1914215a 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml
@@ -150,7 +150,7 @@ entry:
150 starred: 'مقاله‌های برگزیده' 150 starred: 'مقاله‌های برگزیده'
151 archived: 'مقاله‌های بایگانی‌شده' 151 archived: 'مقاله‌های بایگانی‌شده'
152 filtered: 'مقاله‌های فیلترشده' 152 filtered: 'مقاله‌های فیلترشده'
153 # filtered_tags: 'Filtered by tags' 153 # filtered_tags: 'Filtered by tags:'
154 # untagged: 'Untagged entries' 154 # untagged: 'Untagged entries'
155 list: 155 list:
156 number_on_the_page: '{0} هیج مقاله‌ای نیست.|{1} یک مقاله هست.|]1,Inf[ %count% مقاله هست.' 156 number_on_the_page: '{0} هیج مقاله‌ای نیست.|{1} یک مقاله هست.|]1,Inf[ %count% مقاله هست.'
@@ -472,7 +472,6 @@ flashes:
472 rss_updated: 'اطلاعات آر-اس-اس به‌روز شد' 472 rss_updated: 'اطلاعات آر-اس-اس به‌روز شد'
473 tagging_rules_updated: 'برچسب‌گذاری خودکار به‌روز شد' 473 tagging_rules_updated: 'برچسب‌گذاری خودکار به‌روز شد'
474 tagging_rules_deleted: 'قانون برچسب‌گذاری پاک شد' 474 tagging_rules_deleted: 'قانون برچسب‌گذاری پاک شد'
475 user_added: 'کابر "%username%" افزوده شد'
476 rss_token_updated: 'کد آر-اس-اس به‌روز شد' 475 rss_token_updated: 'کد آر-اس-اس به‌روز شد'
477 # annotations_reset: Annotations reset 476 # annotations_reset: Annotations reset
478 # tags_reset: Tags reset 477 # tags_reset: Tags reset
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml
index 74d59e1a..60fa9a39 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml
@@ -1,94 +1,94 @@
1security: 1security:
2 login: 2 login:
3 page_title: 'Bienvenue sur wallabag !' 3 page_title: "Bienvenue sur wallabag !"
4 keep_logged_in: 'Rester connecté' 4 keep_logged_in: "Rester connecté"
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: "Nom dutilisateur"
9 password: 'Mot de passe' 9 password: "Mot de passe"
10 cancel: 'Annuler' 10 cancel: "Annuler"
11 resetting: 11 resetting:
12 description: "Saisissez votre adresse e-mail ci-dessous, nous vous enverrons les instructions pour réinitialiser votre mot de passe." 12 description: "Saisissez votre adresse courriel ci-dessous, nous vous enverrons les instructions pour réinitialiser votre mot de passe."
13 register: 13 register:
14 page_title: 'Se créer un compte' 14 page_title: "Se créer un compte"
15 go_to_account: 'Aller sur votre compte' 15 go_to_account: "Aller sur votre compte"
16 16
17menu: 17menu:
18 left: 18 left:
19 unread: 'Non lus' 19 unread: "Non lus"
20 starred: 'Favoris' 20 starred: "Favoris"
21 archive: 'Lus' 21 archive: "Lus"
22 all_articles: 'Tous les articles' 22 all_articles: "Tous les articles"
23 config: 'Configuration' 23 config: "Configuration"
24 tags: 'Tags' 24 tags: "Tags"
25 internal_settings: 'Configuration interne' 25 internal_settings: "Configuration interne"
26 import: 'Importer' 26 import: "Importer"
27 howto: 'Aide' 27 howto: "Aide"
28 developer: 'Développeur' 28 developer: "Développeur"
29 logout: 'Déconnexion' 29 logout: "Déconnexion"
30 about: 'À propos' 30 about: "À propos"
31 search: 'Recherche' 31 search: "Recherche"
32 save_link: 'Sauvegarder un nouvel article' 32 save_link: "Sauvegarder un nouvel article"
33 back_to_unread: 'Retour aux articles non lus' 33 back_to_unread: "Retour aux articles non lus"
34 users_management: 'Gestion des utilisateurs' 34 users_management: "Gestion des utilisateurs"
35 top: 35 top:
36 add_new_entry: 'Sauvegarder un nouvel article' 36 add_new_entry: "Sauvegarder un nouvel article"
37 search: 'Rechercher' 37 search: "Rechercher"
38 filter_entries: 'Filtrer les articles' 38 filter_entries: "Filtrer les articles"
39 export: 'Exporter' 39 export: "Exporter"
40 search_form: 40 search_form:
41 input_label: 'Saisissez votre terme de recherche' 41 input_label: "Saisissez votre terme de recherche"
42 42
43footer: 43footer:
44 wallabag: 44 wallabag:
45 elsewhere: 'Emportez wallabag avec vous' 45 elsewhere: "Emportez wallabag avec vous"
46 social: 'Social' 46 social: "Social"
47 powered_by: 'propulsé par' 47 powered_by: "propulsé par"
48 about: 'À propos' 48 about: "À propos"
49 stats: Depuis le %user_creation% vous avez lu %nb_archives% articles. Ce qui fait %per_day% par jour ! 49 stats: Depuis le %user_creation% vous avez lu %nb_archives% articles. Ce qui fait %per_day% par jour !
50 50
51config: 51config:
52 page_title: 'Configuration' 52 page_title: "Configuration"
53 tab_menu: 53 tab_menu:
54 settings: 'Paramètres' 54 settings: "Paramètres"
55 rss: 'RSS' 55 rss: "RSS"
56 user_info: 'Mon compte' 56 user_info: "Mon compte"
57 password: 'Mot de passe' 57 password: "Mot de passe"
58 rules: 'Règles de tag automatiques' 58 rules: "Règles de tag automatiques"
59 new_user: 'Créer un compte' 59 new_user: "Créer un compte"
60 form: 60 form:
61 save: 'Enregistrer' 61 save: "Enregistrer"
62 form_settings: 62 form_settings:
63 theme_label: 'Thème' 63 theme_label: "Thème"
64 items_per_page_label: "Nombre d'articles par page" 64 items_per_page_label: "Nombre darticles par page"
65 language_label: 'Langue' 65 language_label: "Langue"
66 reading_speed: 66 reading_speed:
67 label: 'Vitesse de lecture' 67 label: "Vitesse de lecture"
68 help_message: 'Vous pouvez utiliser un outil en ligne pour estimer votre vitesse de lecture :' 68 help_message: "Vous pouvez utiliser un outil en ligne pour estimer votre vitesse de lecture :"
69 100_word: 'Je lis environ 100 mots par minute' 69 100_word: "Je lis environ 100 mots par minute"
70 200_word: 'Je lis environ 200 mots par minute' 70 200_word: "Je lis environ 200 mots par minute"
71 300_word: 'Je lis environ 300 mots par minute' 71 300_word: "Je lis environ 300 mots par minute"
72 400_word: 'Je lis environ 400 mots par minute' 72 400_word: "Je lis environ 400 mots par minute"
73 pocket_consumer_key_label: Clé d'authentification Pocket pour importer les données 73 pocket_consumer_key_label: Clé dauthentification Pocket pour importer les données
74 android_configuration: Configurez votre application Android 74 android_configuration: Configurez votre application Android
75 form_rss: 75 form_rss:
76 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." 76 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."
77 token_label: 'Jeton RSS' 77 token_label: "Jeton RSS"
78 no_token: 'Aucun jeton généré' 78 no_token: "Aucun jeton généré"
79 token_create: 'Créez votre jeton' 79 token_create: "Créez votre jeton"
80 token_reset: 'Réinitialisez votre jeton' 80 token_reset: "Réinitialisez votre jeton"
81 rss_links: 'URL de vos flux RSS' 81 rss_links: "Adresse de vos flux RSS"
82 rss_link: 82 rss_link:
83 unread: 'non lus' 83 unread: "non lus"
84 starred: 'favoris' 84 starred: "favoris"
85 archive: 'lus' 85 archive: "lus"
86 rss_limit: "Nombre d'articles dans le flux" 86 rss_limit: "Nombre darticles dans le flux"
87 form_user: 87 form_user:
88 two_factor_description: "Activer l'authentification double-facteur veut dire que vous allez recevoir un code par email à chaque nouvelle connexion non approuvée." 88 two_factor_description: "Activer l’authentification double-facteur veut dire que vous allez recevoir un code par courriel à chaque nouvelle connexion non approuvée."
89 name_label: 'Nom' 89 name_label: "Nom"
90 email_label: 'Adresse e-mail' 90 email_label: "Adresse courriel"
91 twoFactorAuthentication_label: 'Double authentification' 91 twoFactorAuthentication_label: "Double authentification"
92 delete: 92 delete:
93 title: Supprimer mon compte (attention danger !) 93 title: Supprimer mon compte (attention danger !)
94 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é. 94 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é.
@@ -102,335 +102,335 @@ config:
102 entries: Supprimer TOUS les articles 102 entries: Supprimer TOUS les articles
103 confirm: Êtes-vous vraiment vraiment sûr ? (C'EST IRRÉVERSIBLE) 103 confirm: Êtes-vous vraiment vraiment sûr ? (C'EST IRRÉVERSIBLE)
104 form_password: 104 form_password:
105 old_password_label: 'Mot de passe actuel' 105 old_password_label: "Mot de passe actuel"
106 new_password_label: 'Nouveau mot de passe' 106 new_password_label: "Nouveau mot de passe"
107 repeat_new_password_label: 'Confirmez votre nouveau mot de passe' 107 repeat_new_password_label: "Confirmez votre nouveau mot de passe"
108 form_rules: 108 form_rules:
109 if_label: 'si' 109 if_label: "si"
110 then_tag_as_label: 'alors attribuer les tags' 110 then_tag_as_label: "alors attribuer les tags"
111 delete_rule_label: 'supprimer' 111 delete_rule_label: "supprimer"
112 edit_rule_label: 'éditer' 112 edit_rule_label: "éditer"
113 rule_label: 'Règle' 113 rule_label: "Règle"
114 tags_label: 'Tags' 114 tags_label: "Tags"
115 faq: 115 faq:
116 title: 'FAQ' 116 title: "FAQ"
117 tagging_rules_definition_title: 'Que signifient les règles de tag automatiques ?' 117 tagging_rules_definition_title: "Que signifient les règles de tag automatiques ?"
118 tagging_rules_definition_description: "Ce sont des règles utilisées par wallabag pour classer automatiquement vos nouveaux articles.<br />À chaque fois qu'un nouvel article est ajouté, toutes les règles de tag automatiques seront utilisées afin d'ajouter les tags que vous avez configurés, vous épargnant ainsi l'effort de classifier vos articles manuellement." 118 tagging_rules_definition_description: "Ce sont des règles utilisées par wallabag pour classer automatiquement vos nouveaux articles.<br />À chaque fois qu’un nouvel article est ajouté, toutes les règles de tag automatiques seront utilisées afin d’ajouter les tags que vous avez configurés, vous épargnant ainsi l’effort de classifier vos articles manuellement."
119 how_to_use_them_title: 'Comment les utiliser ?' 119 how_to_use_them_title: "Comment les utiliser ?"
120 how_to_use_them_description: 'Imaginons que voulez attribuer aux nouveaux articles le tag « <i>lecture courte</i> » lorsque le temps de lecture est inférieur à 3 minutes.<br />Dans ce cas, vous devriez mettre « readingTime &lt;= 3 » dans le champ <i>Règle</i> et « <i>lecture courte</i> » dans le champ <i>Tag</i>.<br />Plusieurs tags peuvent être ajoutés simultanément en les séparant par des virgules : « <i>lecture courte, à lire</i> »<br />Des règles complexes peuvent être créées en utilisant des opérateurs prédéfinis: si « <i>readingTime &gt;= 5 AND domainName = \"github.com\"</i> » alors attribuer les tags « <i>lecture longue, github </i> »' 120 how_to_use_them_description: "Imaginons que voulez attribuer aux nouveaux articles le tag « <i>lecture courte</i> » lorsque le temps de lecture est inférieur à 3 minutes.<br />Dans ce cas, vous devriez mettre « readingTime &lt;= 3 » dans le champ <i>Règle</i> et « <i>lecture courte</i> » dans le champ <i>Tag</i>.<br />Plusieurs tags peuvent être ajoutés simultanément en les séparant par des virgules : « <i>lecture courte, à lire</i> »<br />Des règles complexes peuvent être créées en utilisant des opérateurs prédéfinis: si « <i>readingTime &gt;= 5 AND domainName = \"github.com\"</i> » alors attribuer les tags « <i>lecture longue, github</i> »"
121 variables_available_title: 'Quelles variables et opérateurs puis-je utiliser pour écrire des règles ?' 121 variables_available_title: "Quelles variables et opérateurs puis-je utiliser pour écrire des règles ?"
122 variables_available_description: 'Les variables et opérateurs suivants peuvent être utilisés pour écrire des règles de tag automatiques :' 122 variables_available_description: "Les variables et opérateurs suivants peuvent être utilisés pour écrire des règles de tag automatiques :"
123 meaning: 'Signification' 123 meaning: "Signification"
124 variable_description: 124 variable_description:
125 label: 'Variable' 125 label: "Variable"
126 title: "Titre de l'article" 126 title: "Titre de larticle"
127 url: "URL de l'article" 127 url: "Adresse de larticle"
128 isArchived: "Si l'article est archivé ou non" 128 isArchived: "Si larticle est archivé ou non"
129 isStarred: "Si l'article est favori ou non" 129 isStarred: "Si larticle est favori ou non"
130 content: "Le contenu de l'article" 130 content: "Le contenu de larticle"
131 language: "La langue de l'article" 131 language: "La langue de larticle"
132 mimetype: "Le type MIME de l'article" 132 mimetype: "Le type MIME de larticle"
133 readingTime: "Le temps de lecture estimé de l'article, en minutes" 133 readingTime: "Le temps de lecture estimé de larticle, en minutes"
134 domainName: "Le nom de domaine de l'article" 134 domainName: "Le nom de domaine de larticle"
135 operator_description: 135 operator_description:
136 label: 'Opérateur' 136 label: "Opérateur"
137 less_than: 'Moins que…...' 137 less_than: "Moins que…..."
138 strictly_less_than: 'Strictement moins que…' 138 strictly_less_than: "Strictement moins que…"
139 greater_than: 'Plus que…' 139 greater_than: "Plus que…"
140 strictly_greater_than: 'Strictement plus que…' 140 strictly_greater_than: "Strictement plus que…"
141 equal_to: 'Égal à…' 141 equal_to: "Égal à…"
142 not_equal_to: 'Différent de…' 142 not_equal_to: "Différent de…"
143 or: "Une règle OU l'autre" 143 or: "Une règle OU lautre"
144 and: "Une règle ET l'autre" 144 and: "Une règle ET lautre"
145 matches: 'Teste si un <i>sujet</i> correspond à une <i>recherche</i> (non sensible à la casse).<br />Exemple : <code>title matches "football"</code>' 145 matches: "Teste si un <i>sujet</i> correspond à une <i>recherche</i> (non sensible à la casse).<br />Exemple : <code>title matches \"football\"</code>"
146 146
147entry: 147entry:
148 page_titles: 148 page_titles:
149 unread: 'Articles non lus' 149 unread: "Articles non lus"
150 starred: 'Articles favoris' 150 starred: "Articles favoris"
151 archived: 'Articles lus' 151 archived: "Articles lus"
152 filtered: 'Articles filtrés' 152 filtered: "Articles filtrés"
153 filtered_tags: 'Articles filtrés par tags' 153 filtered_tags: "Articles filtrés par tags :"
154 untagged: 'Article sans tag' 154 untagged: "Article sans tag"
155 list: 155 list:
156 number_on_the_page: "{0} Il n'y a pas d'articles.|{1} Il y a un article.|]1,Inf[ Il y a %count% articles." 156 number_on_the_page: "{0} Il ny a pas darticles.|{1} Il y a un article.|]1,Inf[ Il y a %count% articles."
157 reading_time: 'durée de lecture' 157 reading_time: "durée de lecture"
158 reading_time_minutes: 'durée de lecture: %readingTime% min' 158 reading_time_minutes: "durée de lecture: %readingTime% min"
159 reading_time_less_one_minute: 'durée de lecture: <small class="inferieur">&lt;</small> 1 min' 159 reading_time_less_one_minute: "durée de lecture: <small class=\"inferieur\">&lt;</small> 1 min"
160 number_of_tags: '{1}et un autre tag|]1,Inf[et %count% autres tags' 160 number_of_tags: "{1}et un autre tag|]1,Inf[et %count% autres tags"
161 reading_time_minutes_short: '%readingTime% min' 161 reading_time_minutes_short: "%readingTime% min"
162 reading_time_less_one_minute_short: '<small class="inferieur">&lt;</small> 1 min' 162 reading_time_less_one_minute_short: "<small class=\"inferieur\">&lt;</small> 1 min"
163 original_article: 'original' 163 original_article: "original"
164 toogle_as_read: 'Marquer comme lu/non lu' 164 toogle_as_read: "Marquer comme lu/non lu"
165 toogle_as_star: 'Marquer comme favori' 165 toogle_as_star: "Marquer comme favori"
166 delete: 'Supprimer' 166 delete: "Supprimer"
167 export_title: 'Exporter' 167 export_title: "Exporter"
168 filters: 168 filters:
169 title: 'Filtres' 169 title: "Filtres"
170 status_label: 'Status' 170 status_label: "Status"
171 archived_label: 'Lus' 171 archived_label: "Lus"
172 starred_label: 'Favoris' 172 starred_label: "Favoris"
173 unread_label: 'Non lus' 173 unread_label: "Non lus"
174 preview_picture_label: 'A une photo' 174 preview_picture_label: "A une photo"
175 preview_picture_help: 'Photo' 175 preview_picture_help: "Photo"
176 language_label: 'Langue' 176 language_label: "Langue"
177 reading_time: 177 reading_time:
178 label: 'Durée de lecture en minutes' 178 label: "Durée de lecture en minutes"
179 from: 'de' 179 from: "de"
180 to: 'à' 180 to: "à"
181 domain_label: 'Nom de domaine' 181 domain_label: "Nom de domaine"
182 created_at: 182 created_at:
183 label: 'Date de création' 183 label: "Date de création"
184 from: 'de' 184 from: "de"
185 to: 'à' 185 to: "à"
186 action: 186 action:
187 clear: 'Effacer' 187 clear: "Effacer"
188 filter: 'Filtrer' 188 filter: "Filtrer"
189 view: 189 view:
190 left_menu: 190 left_menu:
191 back_to_top: 'Revenir en haut' 191 back_to_top: "Revenir en haut"
192 back_to_homepage: 'Retour' 192 back_to_homepage: "Retour"
193 set_as_read: 'Marquer comme lu' 193 set_as_read: "Marquer comme lu"
194 set_as_unread: 'Marquer comme non lu' 194 set_as_unread: "Marquer comme non lu"
195 set_as_starred: 'Mettre en favori' 195 set_as_starred: "Mettre en favori"
196 view_original_article: 'Article original' 196 view_original_article: "Article original"
197 re_fetch_content: 'Recharger le contenu' 197 re_fetch_content: "Recharger le contenu"
198 delete: 'Supprimer' 198 delete: "Supprimer"
199 add_a_tag: 'Ajouter un tag' 199 add_a_tag: "Ajouter un tag"
200 share_content: 'Partager' 200 share_content: "Partager"
201 share_email_label: 'Email' 201 share_email_label: "Courriel"
202 public_link: 'Lien public' 202 public_link: "Lien public"
203 delete_public_link: 'Supprimer lien public' 203 delete_public_link: "Supprimer le lien public"
204 download: 'Télécharger' 204 download: "Télécharger"
205 print: 'Imprimer' 205 print: "Imprimer"
206 problem: 206 problem:
207 label: 'Un problème ?' 207 label: "Un problème ?"
208 description: "Est-ce que cet article s'affiche mal ?" 208 description: "Est-ce que cet article saffiche mal ?"
209 edit_title: 'Modifier le titre' 209 edit_title: "Modifier le titre"
210 original_article: 'original' 210 original_article: "original"
211 annotations_on_the_entry: '{0} Aucune annotation|{1} Une annotation|]1,Inf[ %count% annotations' 211 annotations_on_the_entry: "{0} Aucune annotation|{1} Une annotation|]1,Inf[ %count% annotations"
212 created_at: 'Date de création' 212 created_at: "Date de création"
213 new: 213 new:
214 page_title: 'Sauvegarder un nouvel article' 214 page_title: "Sauvegarder un nouvel article"
215 placeholder: 'http://website.com' 215 placeholder: "http://website.com"
216 form_new: 216 form_new:
217 url_label: Url 217 url_label: "Adresse"
218 edit: 218 edit:
219 page_title: 'Éditer un article' 219 page_title: "Éditer un article"
220 title_label: 'Titre' 220 title_label: "Titre"
221 url_label: 'Url' 221 url_label: "Adresse"
222 is_public_label: 'Public' 222 is_public_label: "Public"
223 save_label: 'Enregistrer' 223 save_label: "Enregistrer"
224 public: 224 public:
225 shared_by_wallabag: "Cet article a été partagé par <a href='%wallabag_instance%'>wallabag</a>" 225 shared_by_wallabag: "Cet article a été partagé par <a href=\"%wallabag_instance%\">wallabag</a>"
226 226
227about: 227about:
228 page_title: 'À propos' 228 page_title: "À propos"
229 top_menu: 229 top_menu:
230 who_behind_wallabag: "L'équipe derrière wallabag" 230 who_behind_wallabag: "Léquipe derrière wallabag"
231 getting_help: "Besoin d'aide" 231 getting_help: "Besoin daide"
232 helping: 'Aider wallabag' 232 helping: "Aider wallabag"
233 contributors: 'Contributeurs' 233 contributors: "Contributeurs"
234 third_party: 'Librairies tierces' 234 third_party: "Librairies tierces"
235 who_behind_wallabag: 235 who_behind_wallabag:
236 developped_by: 'Développé par' 236 developped_by: "Développé par"
237 website: 'Site web' 237 website: "Site web"
238 many_contributors: 'Et plein de contributeurs ♥ <a href="https://github.com/wallabag/wallabag/graphs/contributors">sur Github</a>' 238 many_contributors: "Et plein de contributeurs ♥ <a href=\"https://github.com/wallabag/wallabag/graphs/contributors\">sur Github</a>"
239 project_website: 'Site web du projet' 239 project_website: "Site web du projet"
240 license: 'Licence' 240 license: "Licence"
241 version: 'Version' 241 version: "Version"
242 getting_help: 242 getting_help:
243 documentation: 'Documentation' 243 documentation: "Documentation"
244 bug_reports: 'Rapport de bugs' 244 bug_reports: "Rapport de bogue"
245 support: '<a href="https://support.wallabag.org">Sur notre site de support</a> ou <a href="https://github.com/wallabag/wallabag/issues">sur GitHub</a>' 245 support: "<a href=\"https://support.wallabag.org\">Sur notre site de support</a> ou <a href=\"https://github.com/wallabag/wallabag/issues\">sur GitHub</a>"
246 helping: 246 helping:
247 description: 'wallabag est gratuit et opensource. Vous pouvez nous aider :' 247 description: "wallabag est gratuit et opensource. Vous pouvez nous aider :"
248 by_contributing: 'en contribuant au projet :' 248 by_contributing: "en contribuant au projet :"
249 by_contributing_2: 'un ticket recense tous nos besoins' 249 by_contributing_2: "un ticket recense tous nos besoins"
250 by_paypal: 'via Paypal' 250 by_paypal: "via Paypal"
251 contributors: 251 contributors:
252 description: "Merci aux contributeurs de l'application web de wallabag" 252 description: "Merci aux contributeurs de lapplication web de wallabag"
253 third_party: 253 third_party:
254 description: 'Voici la liste des dépendances utilisées dans wallabag (et leur license) :' 254 description: "Voici la liste des dépendances utilisées dans wallabag (et leur license) :"
255 package: 'Dépendance' 255 package: "Dépendance"
256 license: 'Licence' 256 license: "Licence"
257 257
258howto: 258howto:
259 page_title: 'Aide' 259 page_title: "Aide"
260 page_description: "Il y a plusieurs façon d'enregistrer un article :" 260 page_description: "Il y a plusieurs façon denregistrer un article :"
261 top_menu: 261 top_menu:
262 browser_addons: 'Extensions de navigateur' 262 browser_addons: "Extensions de navigateur"
263 mobile_apps: 'Applications smartphone' 263 mobile_apps: "Applications smartphone"
264 bookmarklet: 'Bookmarklet' 264 bookmarklet: "Bookmarklet"
265 form: 265 form:
266 description: 'Grâce à ce formulaire' 266 description: "Grâce à ce formulaire"
267 browser_addons: 267 browser_addons:
268 firefox: 'Extension Firefox' 268 firefox: "Extension Firefox"
269 chrome: 'Extension Chrome' 269 chrome: "Extension Chrome"
270 mobile_apps: 270 mobile_apps:
271 android: 271 android:
272 via_f_droid: 'via F-Droid' 272 via_f_droid: "via F-Droid"
273 via_google_play: 'via Google Play' 273 via_google_play: "via Google Play"
274 ios: 'sur iTunes Store' 274 ios: "sur iTunes Store"
275 windows: 'sur Microsoft Store' 275 windows: "sur Microsoft Store"
276 bookmarklet: 276 bookmarklet:
277 description: 'Glissez et déposez ce lien dans votre barre de favoris :' 277 description: "Glissez et déposez ce lien dans votre barre de favoris :"
278 278
279quickstart: 279quickstart:
280 page_title: 'Pour bien débuter' 280 page_title: "Pour bien débuter"
281 more: 'Et plus encore…' 281 more: "Et plus encore…"
282 intro: 282 intro:
283 title: 'Bienvenue sur wallabag !' 283 title: "Bienvenue sur wallabag !"
284 paragraph_1: "Nous allons vous accompagner pour vous faire faire le tour de la maison et vous présenter quelques fonctionnalités qui pourraient vous intéresser pour vous approprier cet outil." 284 paragraph_1: "Nous allons vous accompagner pour vous faire faire le tour de la maison et vous présenter quelques fonctionnalités qui pourraient vous intéresser pour vous approprier cet outil."
285 paragraph_2: 'Suivez-nous !' 285 paragraph_2: "Suivez-nous !"
286 configure: 286 configure:
287 title: "Configurez l'application" 287 title: "Configurez lapplication"
288 description: 'Pour voir une application qui vous correspond, allez voir du côté de la configuration de wallabag.' 288 description: "Pour voir une application qui vous correspond, allez voir du côté de la configuration de wallabag."
289 language: "Changez la langue et le design de l'application" 289 language: "Changez la langue et le design de lapplication"
290 rss: 'Activez les flux RSS' 290 rss: "Activez les flux RSS"
291 tagging_rules: 'Écrivez des règles pour classer automatiquement vos articles' 291 tagging_rules: "Écrivez des règles pour classer automatiquement vos articles"
292 admin: 292 admin:
293 title: 'Administration' 293 title: "Administration"
294 description: "En tant qu'administrateur sur wallabag, vous avez des privilèges qui vous permettent de :" 294 description: "En tant quadministrateur sur wallabag, vous avez des privilèges qui vous permettent de :"
295 new_user: 'Créer un nouvel utilisateur' 295 new_user: "Créer un nouvel utilisateur"
296 analytics: 'Configurer les statistiques' 296 analytics: "Configurer les statistiques"
297 sharing: 'Activer des paramètres de partages' 297 sharing: "Activer des paramètres de partages"
298 export: "Configurer les formats d'export" 298 export: "Configurer les formats dexport"
299 import: "Configurer l'import" 299 import: "Configurer limport"
300 first_steps: 300 first_steps:
301 title: 'Premiers pas' 301 title: "Premiers pas"
302 description: "Maintenant que wallabag est bien configuré, il est temps d'archiver le web. Vous pouvez cliquer sur le signe + dans le coin en haut à droite." 302 description: "Maintenant que wallabag est bien configuré, il est temps d’archiver le web. Vous pouvez cliquer sur le signe + dans le coin en haut à droite."
303 new_article: 'Ajoutez votre premier article' 303 new_article: "Ajoutez votre premier article"
304 unread_articles: 'Et rangez-le !' 304 unread_articles: "Et rangez-le !"
305 migrate: 305 migrate:
306 title: 'Migrer depuis un service existant' 306 title: "Migrer depuis un service existant"
307 description: "Vous êtes un ancien utilisateur d'un service existant ? Nous allons vous aider à récupérer vos données sur wallabag." 307 description: "Vous êtes un ancien utilisateur d’un service existant ? Nous allons vous aider à récupérer vos données sur wallabag."
308 pocket: 'Migrer depuis Pocket' 308 pocket: "Migrer depuis Pocket"
309 wallabag_v1: 'Migrer depuis wallabag v1' 309 wallabag_v1: "Migrer depuis wallabag v1"
310 wallabag_v2: 'Migrer depuis wallabag v2' 310 wallabag_v2: "Migrer depuis wallabag v2"
311 readability: 'Migrer depuis Readability' 311 readability: "Migrer depuis Readability"
312 instapaper: 'Migrer depuis Instapaper' 312 instapaper: "Migrer depuis Instapaper"
313 developer: 313 developer:
314 title: 'Pour les développeurs' 314 title: "Pour les développeurs"
315 description: 'Nous avons aussi pensé aux développeurs : Docker, API, traductions, etc.' 315 description: "Nous avons aussi pensé aux développeurs : Docker, API, traductions, etc."
316 create_application: 'Créer votre application tierce' 316 create_application: "Créer votre application tierce"
317 use_docker: 'Utiliser Docker pour installer wallabag' 317 use_docker: "Utiliser Docker pour installer wallabag"
318 docs: 318 docs:
319 title: 'Documentation complète' 319 title: "Documentation complète"
320 description: "Il y a tellement de fonctionnalités dans wallabag. N'hésitez pas à lire le manuel pour les connaitre et apprendre comment les utiliser." 320 description: "Il y a tellement de fonctionnalités dans wallabag. N’hésitez pas à lire le manuel pour les connaitre et apprendre comment les utiliser."
321 annotate: 'Annoter votre article' 321 annotate: "Annoter votre article"
322 export: 'Convertissez vos articles en ePub ou en PDF' 322 export: "Convertissez vos articles en ePub ou en PDF"
323 search_filters: "Apprenez à utiliser le moteur de recherche et les filtres pour retrouver l'article qui vous intéresse" 323 search_filters: "Apprenez à utiliser le moteur de recherche et les filtres pour retrouver l’article qui vous intéresse"
324 fetching_errors: "Que faire si mon article n'est pas correctement récupéré ?" 324 fetching_errors: "Que faire si mon article nest pas correctement récupéré ?"
325 all_docs: "Et encore plein d'autres choses !" 325 all_docs: "Et encore plein dautres choses !"
326 support: 326 support:
327 title: 'Support' 327 title: "Support"
328 description: 'Parce que vous avez peut-être besoin de nous poser une question, nous sommes disponibles pour vous.' 328 description: "Parce que vous avez peut-être besoin de nous poser une question, nous sommes disponibles pour vous."
329 github: 'Sur GitHub' 329 github: "Sur GitHub"
330 email: 'Par e-mail' 330 email: "Par courriel"
331 gitter: 'Sur Gitter' 331 gitter: "Sur Gitter"
332 332
333tag: 333tag:
334 page_title: 'Tags' 334 page_title: "Tags"
335 list: 335 list:
336 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." 336 number_on_the_page: "{0} Il ny a pas de tag.|{1} Il y a un tag.|]1,Inf[ Il y a %count% tags."
337 see_untagged_entries: 'Voir les articles sans tag' 337 see_untagged_entries: "Voir les articles sans tag"
338 338
339import: 339import:
340 page_title: 'Importer' 340 page_title: "Importer"
341 page_description: "Bienvenue dans l'outil de migration de wallabag. Choisissez ci-dessous le service depuis lequel vous souhaitez migrer." 341 page_description: "Bienvenue dans l’outil de migration de wallabag. Choisissez ci-dessous le service depuis lequel vous souhaitez migrer."
342 action: 342 action:
343 import_contents: 'Importer les contenus' 343 import_contents: "Importer les contenus"
344 form: 344 form:
345 mark_as_read_title: 'Marquer tout comme lu ?' 345 mark_as_read_title: "Marquer tout comme lu ?"
346 mark_as_read_label: 'Marquer tous les contenus importés comme lus' 346 mark_as_read_label: "Marquer tous les contenus importés comme lus"
347 file_label: 'Fichier' 347 file_label: "Fichier"
348 save_label: 'Importer le fichier' 348 save_label: "Importer le fichier"
349 pocket: 349 pocket:
350 page_title: 'Importer > Pocket' 350 page_title: "Importer > Pocket"
351 description: "Cet outil va importer toutes vos données de Pocket. Pocket ne nous autorise pas à récupérer le contenu depuis leur service, donc wallabag doit reparcourir chaque article pour récupérer son contenu." 351 description: "Cet outil va importer toutes vos données de Pocket. Pocket ne nous autorise pas à récupérer le contenu depuis leur service, donc wallabag doit reparcourir chaque article pour récupérer son contenu."
352 config_missing: 352 config_missing:
353 description: "L'import à partir de Pocket n'est pas configuré." 353 description: "Limport à partir de Pocket nest pas configuré."
354 admin_message: "Vous devez définir %keyurls%une clé pour l'API Pocket%keyurle%." 354 admin_message: "Vous devez définir %keyurls%une clé pour lAPI Pocket%keyurle%."
355 user_message: "L'administrateur de votre serveur doit définir une clé pour l'API Pocket." 355 user_message: "Ladministrateur de votre serveur doit définir une clé pour lAPI Pocket."
356 authorize_message: "Vous pouvez importer vos données depuis votre compte Pocket. Vous n'avez qu'à cliquer sur le bouton ci-dessous et à autoriser wallabag à se connecter à getpocket.com." 356 authorize_message: "Vous pouvez importer vos données depuis votre compte Pocket. Vous n’avez qu’à cliquer sur le bouton ci-dessous et à autoriser wallabag à se connecter à getpocket.com."
357 connect_to_pocket: 'Se connecter à Pocket et importer les données' 357 connect_to_pocket: "Se connecter à Pocket et importer les données"
358 wallabag_v1: 358 wallabag_v1:
359 page_title: 'Importer > Wallabag v1' 359 page_title: "Importer > wallabag v1"
360 description: 'Cet outil va importer toutes vos données de wallabag v1. Sur votre page de configuration de wallabag v1, cliquez sur "Export JSON" dans la section "Exporter vos données de wallabag". Vous allez récupérer un fichier "wallabag-export-1-xxxx-xx-xx.json".' 360 description: "Cet outil va importer toutes vos données de wallabag v1. Sur votre page de configuration de wallabag v1, cliquez sur « Export JSON » dans la section « Exporter vos données de wallabag ». Vous allez récupérer un fichier « wallabag-export-1-xxxx-xx-xx.json »."
361 how_to: "Choisissez le fichier de votre export wallabag v1 et cliquez sur le bouton ci-dessous pour l'importer." 361 how_to: "Choisissez le fichier de votre export wallabag v1 et cliquez sur le bouton ci-dessous pour limporter."
362 wallabag_v2: 362 wallabag_v2:
363 page_title: 'Importer > Wallabag v2' 363 page_title: "Importer > wallabag v2"
364 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\"" 364 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 »"
365 readability: 365 readability:
366 page_title: 'Importer > Readability' 366 page_title: "Importer > Readability"
367 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 email avec un lien pour télécharger le json.' 367 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."
368 how_to: "Choisissez le fichier de votre export Readability et cliquez sur le bouton ci-dessous pour l'importer." 368 how_to: "Choisissez le fichier de votre export Readability et cliquez sur le bouton ci-dessous pour limporter."
369 worker: 369 worker:
370 enabled: "Les imports sont asynchrones. Une fois l'import commencé un worker externe traitera les messages un par un. Le service activé est :" 370 enabled: "Les imports sont asynchrones. Une fois l’import commencé un worker externe traitera les messages un par un. Le service activé est :"
371 download_images_warning: "Vous avez configuré le téléchagement des images pour vos articles. Combiné à l'import classique, cette opération peut être très très longue (voire échouer). Nous vous conseillons <strong>vivement</strong> d'activer les imports asynchrones." 371 download_images_warning: "Vous avez configuré le téléchagement des images pour vos articles. Combiné à l'import classique, cette opération peut être très très longue (voire échouer). Nous vous conseillons <strong>vivement</strong> d'activer les imports asynchrones."
372 firefox: 372 firefox:
373 page_title: 'Import > Firefox' 373 page_title: "Import > Firefox"
374 description: "Cet outil va vous permettre d'importer tous vos marques-pages de Firefox. Ouvrez le panneau des marques-pages (Ctrl+Maj+O), puis dans « Importation et sauvegarde », choisissez « Sauvegarde... ». Vous allez récupérer un fichier .json. </p>" 374 description: "Cet outil va vous permettre d’importer tous vos marques-pages de Firefox. Ouvrez le panneau des marques-pages (Ctrl+Maj+O), puis dans « Importation et sauvegarde », choisissez « Sauvegarde... ». Vous allez récupérer un fichier .json. </p>"
375 how_to: "Choisissez le fichier de sauvegarde de vos marques-page et cliquez sur le bouton pour l'importer. Soyez avertis que le processus peut prendre un temps assez long car tous les articles doivent être récupérés en ligne." 375 how_to: "Choisissez le fichier de sauvegarde de vos marques-page et cliquez sur le bouton pour l’importer. Soyez avertis que le processus peut prendre un temps assez long car tous les articles doivent être récupérés en ligne."
376 chrome: 376 chrome:
377 page_title: 'Import > Chrome' 377 page_title: "Import > Chrome"
378 description: "Cet outil va vous permettre d'importer tous vos marques-pages de Google Chrome/Chromium. Pour Google Chrome, la situation du fichier dépend de votre système d'exploitation : <ul><li>Sur GNU/Linux, allez dans le répertoire <code>~/.config/google-chrome/Default/</code></li><li>Sous Windows, il devrait se trouver à <code>%LOCALAPPDATA%\\Google\\Chrome\\User Data\\Default</code></li><li>Sur OS X, il devrait se trouver dans le fichier <code>~/Library/Application Support/Google/Chrome/Default/Bookmarks</code></li></ul>Une fois que vous y êtes, copiez le fichier Bookmarks à un endroit où vous le retrouverez.<em><br>Notez que si vous utilisez Chromium à la place de Chrome, vous devez corriger les chemins en conséquence.</em></p>" 378 description: "Cet outil va vous permettre d’importer tous vos marques-pages de Google Chrome/Chromium. Pour Google Chrome, la situation du fichier dépend de votre système d’exploitation : <ul><li>Sur GNU/Linux, allez dans le répertoire <code>~/.config/google-chrome/Default/</code></li><li>Sous Windows, il devrait se trouver à <code>%LOCALAPPDATA%\\Google\\Chrome\\User Data\\Default</code></li><li>Sur OS X, il devrait se trouver dans le fichier <code>~/Library/Application Support/Google/Chrome/Default/Bookmarks</code></li></ul>Une fois que vous y êtes, copiez le fichier Bookmarks à un endroit où vous le retrouverez.<em><br>Notez que si vous utilisez Chromium à la place de Chrome, vous devez corriger les chemins en conséquence.</em></p>"
379 how_to: "Choisissez le fichier de sauvegarde de vos marques-page et cliquez sur le bouton pour l'importer. Soyez avertis que le processus peut prendre un temps assez long car tous les articles doivent être récupérés en ligne." 379 how_to: "Choisissez le fichier de sauvegarde de vos marques-page et cliquez sur le bouton pour l’importer. Soyez avertis que le processus peut prendre un temps assez long car tous les articles doivent être récupérés en ligne."
380 instapaper: 380 instapaper:
381 page_title: 'Import > Instapaper' 381 page_title: "Import > Instapaper"
382 description: 'Sur la page des paramètres (https://www.instapaper.com/user), cliquez sur "Download .CSV file" dans la section "Export". Un fichier CSV se téléchargera ("instapaper-export.csv").' 382 description: "Sur la page des paramètres (https://www.instapaper.com/user), cliquez sur « Download .CSV file » dans la section « Export ». Un fichier CSV sera téléchargé (« instapaper-export.csv »)."
383 how_to: "Choisissez le fichier de votre export Instapaper et cliquez sur le bouton ci-dessous pour l'importer." 383 how_to: "Choisissez le fichier de votre export Instapaper et cliquez sur le bouton ci-dessous pour limporter."
384 384
385developer: 385developer:
386 page_title: 'Développeur' 386 page_title: "Développeur"
387 welcome_message: "Bienvenue sur l'API de wallabag" 387 welcome_message: "Bienvenue sur lAPI de wallabag"
388 documentation: 'Documentation' 388 documentation: "Documentation"
389 how_to_first_app: 'Comment créer votre première application' 389 how_to_first_app: "Comment créer votre première application"
390 full_documentation: "Voir la documentation complète de l'API" 390 full_documentation: "Voir la documentation complète de lAPI"
391 list_methods: "Lister toutes les méthodes de l'API" 391 list_methods: "Lister toutes les méthodes de lAPI"
392 clients: 392 clients:
393 title: 'Clients' 393 title: "Clients"
394 create_new: 'Créer un nouveau client' 394 create_new: "Créer un nouveau client"
395 existing_clients: 395 existing_clients:
396 title: 'Les clients existants' 396 title: "Les clients existants"
397 field_id: 'ID Client' 397 field_id: "ID Client"
398 field_secret: 'Clé secrète' 398 field_secret: "Clé secrète"
399 field_uris: 'URLs de redirection' 399 field_uris: "Adresse de redirection"
400 field_grant_types: 'Type de privilège accordé' 400 field_grant_types: "Type de privilège accordé"
401 no_client: 'Aucun client pour le moment' 401 no_client: "Aucun client pour le moment"
402 remove: 402 remove:
403 warn_message_1: 'Vous avez la possibilité de supprimer le client %name%. Cette action est IRRÉVERSIBLE !' 403 warn_message_1: "Vous avez la possibilité de supprimer le client %name%. Cette action est IRRÉVERSIBLE !"
404 warn_message_2: "Si vous supprimez le client %name%, toutes les applications qui l'utilisaient ne fonctionneront plus avec votre compte wallabag." 404 warn_message_2: "Si vous supprimez le client %name%, toutes les applications qui l’utilisaient ne fonctionneront plus avec votre compte wallabag."
405 action: 'Supprimer le client %name%' 405 action: "Supprimer le client %name%"
406 client: 406 client:
407 page_title: 'Développeur > Nouveau client' 407 page_title: "Développeur > Nouveau client"
408 page_description: "Vous allez créer un nouveau client. Merci de remplir l'url de redirection vers votre application." 408 page_description: "Vous allez créer un nouveau client. Merci de remplir l’adresse de redirection vers votre application."
409 form: 409 form:
410 name_label: "Nom du client" 410 name_label: "Nom du client"
411 redirect_uris_label: 'URLs de redirection (optionnel)' 411 redirect_uris_label: "Adresses de redirection (optionnel)"
412 save_label: 'Créer un nouveau client' 412 save_label: "Créer un nouveau client"
413 action_back: 'Retour' 413 action_back: "Retour"
414 client_parameter: 414 client_parameter:
415 page_title: 'Développeur > Les paramètres de votre client' 415 page_title: "Développeur > Les paramètres de votre client"
416 page_description: 'Voilà les paramètres de votre client' 416 page_description: "Voilà les paramètres de votre client"
417 field_name: 'Nom du client' 417 field_name: "Nom du client"
418 field_id: 'ID Client' 418 field_id: "ID client"
419 field_secret: 'Clé secrète' 419 field_secret: "Clé secrète"
420 back: 'Retour' 420 back: "Retour"
421 read_howto: 'Lire "comment créer ma première application"' 421 read_howto: "Lire « comment créer ma première application »"
422 howto: 422 howto:
423 page_title: 'Développeur > Comment créer votre première application' 423 page_title: "Développeur > Comment créer votre première application"
424 description: 424 description:
425 paragraph_1: "Les commandes suivantes utilisent la <a href=\"https://github.com/jkbrzt/httpie\">librarie HTTPie</a>. Assurez-vous qu'elle soit installée avant de l'utiliser." 425 paragraph_1: "Les commandes suivantes utilisent la <a href=\"https://github.com/jkbrzt/httpie\">librarie HTTPie</a>. Assurez-vous qu’elle soit installée avant de l’utiliser."
426 paragraph_2: "Vous avez besoin d'un token pour échanger entre votre application et l'API de wallabag." 426 paragraph_2: "Vous avez besoin dun token pour échanger entre votre application et lAPI de wallabag."
427 paragraph_3: 'Pour créer un token, vous devez <a href="%link%">créer un nouveau client</a>.' 427 paragraph_3: "Pour créer un token, vous devez <a href=\"%link%\">créer un nouveau client</a>."
428 paragraph_4: 'Maintenant créez votre token (remplacer client_id, client_secret, username et password avec les bonnes valeurs):' 428 paragraph_4: "Maintenant créez votre token (remplacer client_id, client_secret, username et password avec les bonnes valeurs):"
429 paragraph_5: "L'API vous retournera une réponse comme ça :" 429 paragraph_5: "LAPI vous retournera une réponse comme ça :"
430 paragraph_6: "L'access_token doit être utilisé pour faire un appel à l'API. Par exemple :" 430 paragraph_6: "Laccess_token doit être utilisé pour faire un appel à lAPI. Par exemple :"
431 paragraph_7: "Cet appel va retourner tous les articles de l'utilisateur." 431 paragraph_7: "Cet appel va retourner tous les articles de lutilisateur."
432 paragraph_8: "Si vous voulez toutes les méthodes de l'API, jetez un oeil <a href=\"%link%\">à la documentation de l'API</a>." 432 paragraph_8: "Si vous voulez toutes les méthodes de l’API, jetez un oeil <a href=\"%link%\">à la documentation de l’API</a>."
433 back: 'Retour' 433 back: "Retour"
434 434
435user: 435user:
436 page_title: Gestion des utilisateurs 436 page_title: Gestion des utilisateurs
@@ -444,20 +444,20 @@ user:
444 no: Non 444 no: Non
445 create_new_one: Créer un nouvel utilisateur 445 create_new_one: Créer un nouvel utilisateur
446 form: 446 form:
447 username_label: "Nom d'utilisateur" 447 username_label: "Nom dutilisateur"
448 name_label: 'Nom' 448 name_label: "Nom"
449 password_label: 'Mot de passe' 449 password_label: "Mot de passe"
450 repeat_new_password_label: 'Confirmez votre nouveau mot de passe' 450 repeat_new_password_label: "Confirmez votre nouveau mot de passe"
451 plain_password_label: 'Mot de passe en clair' 451 plain_password_label: "Mot de passe en clair"
452 email_label: 'Adresse e-mail' 452 email_label: "Adresse courriel"
453 enabled_label: 'Activé' 453 enabled_label: "Activé"
454 locked_label: 'Bloqué' 454 locked_label: "Bloqué"
455 last_login_label: 'Dernière connexion' 455 last_login_label: "Dernière connexion"
456 twofactor_label: Double authentification 456 twofactor_label: "Double authentification"
457 save: Sauvegarder 457 save: "Sauvegarder"
458 delete: Supprimer 458 delete: "Supprimer"
459 delete_confirm: Êtes-vous r? 459 delete_confirm: "Voulez-vous vraiment ?"
460 back_to_list: Revenir à la liste 460 back_to_list: "Revenir à la liste"
461 461
462error: 462error:
463 page_title: Une erreur est survenue 463 page_title: Une erreur est survenue
@@ -465,47 +465,46 @@ error:
465flashes: 465flashes:
466 config: 466 config:
467 notice: 467 notice:
468 config_saved: 'Les paramètres ont bien été mis à jour. Certains seront pris en compte après déconnexion.' 468 config_saved: "Les paramètres ont bien été mis à jour. Certains seront pris en compte après déconnexion."
469 password_updated: 'Votre mot de passe a bien été mis à jour' 469 password_updated: "Votre mot de passe a bien été mis à jour"
470 password_not_updated_demo: "En démo, vous ne pouvez pas changer le mot de passe de cet utilisateur." 470 password_not_updated_demo: "En démo, vous ne pouvez pas changer le mot de passe de cet utilisateur."
471 user_updated: 'Vos informations personnelles ont bien été mises à jour' 471 user_updated: "Vos informations personnelles ont bien été mises à jour"
472 rss_updated: 'La configuration des flux RSS a bien été mise à jour' 472 rss_updated: "La configuration des flux RSS a bien été mise à jour"
473 tagging_rules_updated: 'Règles mises à jour' 473 tagging_rules_updated: "Règles mises à jour"
474 tagging_rules_deleted: 'Règle supprimée' 474 tagging_rules_deleted: "Règle supprimée"
475 user_added: 'Utilisateur "%username%" ajouté' 475 rss_token_updated: "Jeton RSS mis à jour"
476 rss_token_updated: 'Jeton RSS mis à jour'
477 annotations_reset: Annotations supprimées 476 annotations_reset: Annotations supprimées
478 tags_reset: Tags supprimés 477 tags_reset: Tags supprimés
479 entries_reset: Articles supprimés 478 entries_reset: Articles supprimés
480 entry: 479 entry:
481 notice: 480 notice:
482 entry_already_saved: 'Article déjà sauvegardé le %date%' 481 entry_already_saved: "Article déjà sauvergardé le %date%"
483 entry_saved: 'Article enregistré' 482 entry_saved: "Article enregistré"
484 entry_saved_failed: 'Article enregistré mais impossible de récupérer le contenu' 483 entry_saved_failed: "Article enregistré mais impossible de récupérer le contenu"
485 entry_updated: 'Article mis à jour' 484 entry_updated: "Article mis à jour"
486 entry_reloaded: 'Article rechargé' 485 entry_reloaded: "Article rechargé"
487 entry_reloaded_failed: "Article mis à jour mais impossible de récupérer le contenu" 486 entry_reloaded_failed: "Article mis à jour mais impossible de récupérer le contenu"
488 entry_archived: 'Article marqué comme lu' 487 entry_archived: "Article marqué comme lu"
489 entry_unarchived: 'Article marqué comme non lu' 488 entry_unarchived: "Article marqué comme non lu"
490 entry_starred: 'Article ajouté dans les favoris' 489 entry_starred: "Article ajouté dans les favoris"
491 entry_unstarred: 'Article retiré des favoris' 490 entry_unstarred: "Article retiré des favoris"
492 entry_deleted: 'Article supprimé' 491 entry_deleted: "Article supprimé"
493 tag: 492 tag:
494 notice: 493 notice:
495 tag_added: 'Tag ajouté' 494 tag_added: "Tag ajouté"
496 import: 495 import:
497 notice: 496 notice:
498 failed: "L'import a échoué, veuillez ré-essayer" 497 failed: "Limport a échoué, veuillez ré-essayer"
499 failed_on_file: "Erreur lors du traitement de l'import. Vérifier votre fichier." 498 failed_on_file: "Erreur lors du traitement de limport. Vérifiez votre fichier."
500 summary: "Rapport d'import: %imported% importés, %skipped% déjà présents." 499 summary: "Rapport dimport : %imported% importés, %skipped% déjà présents."
501 summary_with_queue: "Rapport d'import: %queued% en cours de traitement." 500 summary_with_queue: "Rapport dimport: %queued% en cours de traitement."
502 error: 501 error:
503 redis_enabled_not_installed: Redis est activé pour les imports asynchrones mais <u>impossible de s'y connecter</u>. Vérifier la configuration de Redis. 502 redis_enabled_not_installed: "Redis est activé pour les imports asynchrones mais <u>impossible de s’y connecter</u>. Vérifier la configuration de Redis."
504 rabbit_enabled_not_installed: RabbitMQ est activé pour les imports asynchrones mais <u>impossible de s'y connecter</u>. Vérifier la configuration de RabbitMQ. 503 rabbit_enabled_not_installed: "RabbitMQ est activé pour les imports asynchrones mais <u>impossible de s’y connecter</u>. Vérifier la configuration de RabbitMQ."
505 developer: 504 developer:
506 notice: 505 notice:
507 client_created: 'Nouveau client %name% créé' 506 client_created: "Nouveau client %name% créé"
508 client_deleted: 'Client %name% supprimé' 507 client_deleted: "Client %name% supprimé"
509 user: 508 user:
510 notice: 509 notice:
511 added: 'Utilisateur "%username%" ajouté' 510 added: 'Utilisateur "%username%" ajouté'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.it.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.it.yml
index a448b602..7f401684 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.it.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.it.yml
@@ -150,7 +150,7 @@ entry:
150 starred: 'Contenuti preferiti' 150 starred: 'Contenuti preferiti'
151 archived: 'Contenuti archiviati' 151 archived: 'Contenuti archiviati'
152 filtered: 'Contenuti filtrati' 152 filtered: 'Contenuti filtrati'
153 # filtered_tags: 'Filtered by tags' 153 # filtered_tags: 'Filtered by tags:'
154 # untagged: 'Untagged entries' 154 # untagged: 'Untagged entries'
155 list: 155 list:
156 number_on_the_page: "{0} Non ci sono contenuti.|{1} C'è un contenuto.|]1,Inf[ Ci sono %count% contenuti." 156 number_on_the_page: "{0} Non ci sono contenuti.|{1} C'è un contenuto.|]1,Inf[ Ci sono %count% contenuti."
@@ -472,7 +472,6 @@ flashes:
472 rss_updated: 'Informazioni RSS aggiornate' 472 rss_updated: 'Informazioni RSS aggiornate'
473 tagging_rules_updated: 'Regole di tagging aggiornate' 473 tagging_rules_updated: 'Regole di tagging aggiornate'
474 tagging_rules_deleted: 'Regola di tagging aggiornate' 474 tagging_rules_deleted: 'Regola di tagging aggiornate'
475 user_added: 'Utente "%username%" aggiunto'
476 rss_token_updated: 'RSS token aggiornato' 475 rss_token_updated: 'RSS token aggiornato'
477 # annotations_reset: Annotations reset 476 # annotations_reset: Annotations reset
478 # tags_reset: Tags reset 477 # tags_reset: Tags reset
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml
index a61f7cdd..c3282b0e 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml
@@ -150,7 +150,7 @@ entry:
150 starred: 'Articles favorits' 150 starred: 'Articles favorits'
151 archived: 'Articles legits' 151 archived: 'Articles legits'
152 filtered: 'Articles filtrats' 152 filtered: 'Articles filtrats'
153 filtered_tags: 'Filtats per etiquetas' 153 filtered_tags: 'Filtats per etiquetas:'
154 untagged: 'Articles sens etiqueta' 154 untagged: 'Articles sens etiqueta'
155 list: 155 list:
156 number_on_the_page: "{0} I a pas cap d'article.|{1} I a un article.|]1,Inf[ I a %count% articles." 156 number_on_the_page: "{0} I a pas cap d'article.|{1} I a un article.|]1,Inf[ I a %count% articles."
@@ -472,7 +472,6 @@ flashes:
472 rss_updated: 'La configuracion dels fluxes RSS es ben estada mesa a jorn' 472 rss_updated: 'La configuracion dels fluxes RSS es ben estada mesa a jorn'
473 tagging_rules_updated: 'Règlas misa a jorn' 473 tagging_rules_updated: 'Règlas misa a jorn'
474 tagging_rules_deleted: 'Règla suprimida' 474 tagging_rules_deleted: 'Règla suprimida'
475 user_added: 'Utilizaire "%username%" ajustat'
476 rss_token_updated: 'Geton RSS mes a jorn' 475 rss_token_updated: 'Geton RSS mes a jorn'
477 # annotations_reset: Annotations reset 476 # annotations_reset: Annotations reset
478 # tags_reset: Tags reset 477 # tags_reset: Tags reset
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml
index a7387b79..87731faf 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml
@@ -150,7 +150,7 @@ entry:
150 starred: 'Wpisy oznaczone gwiazdką' 150 starred: 'Wpisy oznaczone gwiazdką'
151 archived: 'Zarchiwizowane wpisy' 151 archived: 'Zarchiwizowane wpisy'
152 filtered: 'Odfiltrowane wpisy' 152 filtered: 'Odfiltrowane wpisy'
153 filtered_tags: 'Filtrowane po tagach' 153 filtered_tags: 'Filtrowane po tagach:'
154 untagged: 'Odtaguj wpisy' 154 untagged: 'Odtaguj wpisy'
155 list: 155 list:
156 number_on_the_page: '{0} Nie ma wpisów.|{1} Jest jeden wpis.|]1,Inf[ Są %count% wpisy.' 156 number_on_the_page: '{0} Nie ma wpisów.|{1} Jest jeden wpis.|]1,Inf[ Są %count% wpisy.'
@@ -472,7 +472,6 @@ flashes:
472 rss_updated: 'Informacje RSS zaktualizowane' 472 rss_updated: 'Informacje RSS zaktualizowane'
473 tagging_rules_updated: 'Reguły tagowania zaktualizowane' 473 tagging_rules_updated: 'Reguły tagowania zaktualizowane'
474 tagging_rules_deleted: 'Reguła tagowania usunięta' 474 tagging_rules_deleted: 'Reguła tagowania usunięta'
475 user_added: 'Użytkownik "%username%" dodany'
476 rss_token_updated: 'Token kanału RSS zaktualizowany' 475 rss_token_updated: 'Token kanału RSS zaktualizowany'
477 annotations_reset: Zresetuj adnotacje 476 annotations_reset: Zresetuj adnotacje
478 tags_reset: Zresetuj tagi 477 tags_reset: Zresetuj tagi
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.pt.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.pt.yml
new file mode 100644
index 00000000..c1c60430
--- /dev/null
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.pt.yml
@@ -0,0 +1,491 @@
1security:
2 login:
3 page_title: 'Bem vindo ao wallabag!'
4 keep_logged_in: 'Mantenha-me autenticado'
5 forgot_password: 'Esqueceu a senha?'
6 submit: 'Login'
7 register: 'Registre-se'
8 username: 'Nome de usuário'
9 password: 'Senha'
10 cancel: 'Cancelar'
11 resetting:
12 description: 'Digite seu endereço de e-mail abaixo e enviaremos instruções para resetar sua senha.'
13 register:
14 page_title: 'Criar uma conta'
15 go_to_account: 'Ir para sua conta'
16
17menu:
18 left:
19 unread: 'Não lido'
20 starred: 'Destacado'
21 archive: 'Arquivo'
22 all_articles: 'Todas as entradas'
23 config: 'Configurações'
24 tags: 'Tags'
25 internal_settings: 'Configurações Internas'
26 import: 'Importar'
27 howto: 'How to'
28 developer: 'Desenvolvedor'
29 logout: 'Sair'
30 about: 'Sobre'
31 search: 'Pesquisa'
32 save_link: 'Salvar um link'
33 back_to_unread: 'Voltar para os artigos não lidos'
34 users_management: 'Gestão de Usuários'
35 top:
36 add_new_entry: 'Adicionar uma nova entrada'
37 search: 'Pesquisa'
38 filter_entries: 'Filtrar entradas'
39 export: 'Exportar'
40 search_form:
41 input_label: 'Digite aqui sua pesquisa'
42
43footer:
44 wallabag:
45 elsewhere: 'Leve o wallabag com você'
46 social: 'Social'
47 powered_by: 'provido por'
48 about: 'Sobre'
49 stats: 'Desde %user_creation% você leu %nb_archives% artigos. Isso é %per_day% por dia!'
50
51config:
52 page_title: 'Config'
53 tab_menu:
54 settings: 'Configurações'
55 rss: 'RSS'
56 user_info: 'Informação do Usuário'
57 password: 'Senha'
58 rules: 'Regras de tags'
59 new_user: 'Adicionar um usuário'
60 form:
61 save: 'Salvar'
62 form_settings:
63 theme_label: 'Tema'
64 items_per_page_label: 'Itens por página'
65 language_label: 'Idioma'
66 reading_speed:
67 label: 'Velocidade de leitura'
68 help_message: 'Você pode usar ferramentas online para estimar sua velocidade de leitura:'
69 100_word: 'Posso ler ~100 palavras por minuto'
70 200_word: 'Posso ler ~200 palavras por minuto'
71 300_word: 'Posso ler ~300 palavras por minuto'
72 400_word: 'Posso ler ~400 palavras por minuto'
73 pocket_consumer_key_label: 'Chave do consumidor do Pocket para importar conteúdo'
74 form_rss:
75 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.'
76 token_label: 'Token RSS'
77 no_token: 'Nenhum Token'
78 token_create: 'Criar seu token'
79 token_reset: 'Gerar novamente seu token'
80 rss_links: 'Links RSS'
81 rss_link:
82 unread: 'não lido'
83 starred: 'destacado'
84 archive: 'arquivado'
85 rss_limit: 'Número de itens no feed'
86 form_user:
87 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.'
88 name_label: 'Nome'
89 email_label: 'E-mail'
90 twoFactorAuthentication_label: 'Autenticação de dois passos'
91 form_password:
92 old_password_label: 'Senha atual'
93 new_password_label: 'Nova senha'
94 repeat_new_password_label: 'Repita a nova senha'
95 form_rules:
96 if_label: 'if'
97 then_tag_as_label: 'então coloque a tag'
98 delete_rule_label: 'apagar'
99 edit_rule_label: 'editar'
100 rule_label: 'Regras'
101 tags_label: 'Tags'
102 faq:
103 title: 'FAQ'
104 tagging_rules_definition_title: 'O que as « regras de tags » significam?'
105 tagging_rules_definition_description: 'São regras usadas pelo Wallabag para automaticamente adicionar tags em novos artigos.<br />Cada vez que um novo artigo é adicionado, todas as regras de tags podem ser usadas para adicionar as tags que você configurou, ajudando-o com o problema de classificar manualmente seus artigos.'
106 how_to_use_them_title: 'Como eu as utilizo?'
107 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> »'
108 variables_available_title: 'Quais variáveis e operadores eu posso usar para escrever regras?'
109 variables_available_description: 'As seguintes variáveis e operadores podem ser usados para criar regras de tags:'
110 meaning: 'Meaning'
111 variable_description:
112 label: 'Variável'
113 title: 'Título da entrada'
114 url: 'URL da entrada'
115 isArchived: 'Se a entrada está arquivada ou não'
116 isDestacado: 'Se a entrada está destacada ou não'
117 content: 'O conteúdo da entrada'
118 language: 'O idioma da entrada'
119 mimetype: 'O mime-type da entrada'
120 readingTime: 'O tempo estimado de leitura da entrada, em minutos'
121 domainName: 'O domínio da entrada'
122 operator_description:
123 label: 'Operador'
124 less_than: 'Menor que...'
125 strictly_less_than: 'Estritamente menor que...'
126 greater_than: 'Maior que...'
127 strictly_greater_than: 'Estritamente maior que...'
128 equal_to: 'Igual a...'
129 not_equal_to: 'Diferente de...'
130 or: 'Uma regra OU outra'
131 and: 'Uma regra E outra'
132 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>'
133
134entry:
135 page_titles:
136 unread: 'Entradas não lidas'
137 starred: 'Entradas destacadas'
138 archived: 'Entradas arquivadas'
139 filtered: 'Entradas filtradas'
140 filtered_tags: 'Filtrar por tags:'
141 untagged: 'Entradas sem tags'
142 list:
143 number_on_the_page: '{0} Não existem entradas.|{1} Existe uma entrada.|]1,Inf[ Existem %count% entradas.'
144 reading_time: 'tempo estimado de leitura'
145 reading_time_minutes: 'tempo estimado de leitura: %readingTime% min'
146 reading_time_less_one_minute: 'tempo estimado de leitura: <small class="inferieur">&lt;</small> 1 min'
147 number_of_tags: '{1}e uma outra tag|]1,Inf[e %count% outras tags'
148 reading_time_minutes_short: '%readingTime% min'
149 reading_time_less_one_minute_short: '<small class="inferieur">&lt;</small> 1 min'
150 original_article: 'original'
151 toogle_as_read: 'Marcar como lido'
152 toogle_as_star: 'Marcar como destacado'
153 delete: 'Apagar'
154 export_title: 'Exportar'
155 filters:
156 title: 'Filtros'
157 status_label: 'Status'
158 archived_label: 'Arquivado'
159 starred_label: 'Destacado'
160 unread_label: 'Não Lido'
161 preview_picture_label: 'Possui uma imagem de preview'
162 preview_picture_help: 'Imagem de preview'
163 language_label: 'Idioma'
164 reading_time:
165 label: 'Tempo de leitura em minutos'
166 from: 'de'
167 to: 'para'
168 domain_label: 'Nome do domínio'
169 created_at:
170 label: 'Data de criação'
171 from: 'de'
172 to: 'para'
173 action:
174 clear: 'Limpar'
175 filter: 'Filtro'
176 view:
177 left_menu:
178 back_to_top: 'Voltar ao topo'
179 back_to_homepage: 'Voltar'
180 set_as_read: 'Marcar como lido'
181 set_as_unread: 'Marcar como não lido'
182 set_as_starred: 'Alternar destaque'
183 view_original_article: 'Artigo original'
184 re_fetch_content: 'Recapturar o conteúdo'
185 delete: 'Apagar'
186 add_a_tag: 'Adicionar uma tag'
187 share_content: 'Compartilhar'
188 share_email_label: 'E-mail'
189 public_link: 'link público'
190 delete_public_link: 'apagar link público'
191 download: 'Download'
192 print: 'Imprimir'
193 problem:
194 label: 'Problemas?'
195 description: 'este artigo aparece errado?'
196 edit_title: 'Editar título'
197 original_article: 'original'
198 annotations_on_the_entry: '{0} Sem anotações|{1} Uma anotação|]1,Inf[ %nbAnnotations% anotações'
199 created_at: 'Data de criação'
200 new:
201 page_title: 'Salvar nova entrada'
202 placeholder: 'http://website.com'
203 form_new:
204 url_label: Url
205 edit:
206 page_title: 'Editar uma entrada'
207 title_label: 'Título'
208 url_label: 'Url'
209 is_public_label: 'Público'
210 save_label: 'Salvar'
211 public:
212 shared_by_wallabag: "Este artigo foi compartilhado pelo <a href='%wallabag_instance%'>wallabag</a>"
213
214about:
215 page_title: 'Sobre'
216 top_menu:
217 who_behind_wallabag: 'Quem está por trás do wallabag'
218 getting_help: 'Obtendo ajuda'
219 helping: 'Ajudando o wallabag'
220 contributors: 'Contribuidores'
221 third_party: 'Bibliotecas terceiras'
222 who_behind_wallabag:
223 developped_by: 'Desenvolvido por'
224 website: 'website'
225 many_contributors: 'E muitos outros contribuidores ♥ <a href="https://github.com/wallabag/wallabag/graphs/contributors">no Github</a>'
226 project_website: 'Website do projeto'
227 license: 'Licença'
228 version: 'Versão'
229 getting_help:
230 documentation: 'Documentação'
231 bug_reports: 'Informar bugs'
232 support: '<a href="https://support.wallabag.org">Em nosso site de suporte</a> ou <a href="https://github.com/wallabag/wallabag/issues">no GitHub</a>'
233 helping:
234 description: 'wallabag é livre e software livre. Você pode nos ajudar:'
235 by_contributing: 'contribuindo com o projeto:'
236 by_contributing_2: 'uma lista de todas as nossas necessidades'
237 by_paypal: 'via Paypal'
238 contributors:
239 description: 'Obrigado por contribuir com a aplicação web wallabag'
240 third_party:
241 description: 'Aqui está a lista de bibliotecas terceiras usadas no wallabag (com suas licenças):'
242 package: 'Pacote'
243 license: 'Licença'
244
245howto:
246 page_title: 'How to'
247 page_description: 'Existem diferentes formas de salvar um artigo:'
248 top_menu:
249 browser_addons: 'Extensões de navegadores'
250 mobile_apps: "App's móveis"
251 bookmarklet: 'Bookmarklet'
252 form:
253 description: 'Obrigado por este formulário'
254 browser_addons:
255 firefox: 'Extensão padrão do Firefox'
256 chrome: 'Extensão do Chrome'
257 mobile_apps:
258 android:
259 via_f_droid: 'via F-Droid'
260 via_google_play: 'via Google Play'
261 ios: 'na iTunes Store'
262 windows: 'na Microsoft Store'
263 bookmarklet:
264 description: 'Arraste e solve este link na sua barra de favoritos:'
265
266quickstart:
267 page_title: 'Começo Rápido'
268 more: 'Mais...'
269 intro:
270 title: 'Bem-vindo ao wallabag!'
271 paragraph_1: 'Nós podemos acompanhar você em sua visita ao wallabag e mostrar algumas funcionalidades que podem lhe interessar.'
272 paragraph_2: 'Siga-nos!'
273 configure:
274 title: 'Configurar a aplicação'
275 description: 'Para ter uma aplicação que atende você, dê uma olhada na configuração do wallabag.'
276 language: 'Alterar idioma e design'
277 rss: 'Habilitar feeds RSS'
278 tagging_rules: 'Escrever regras para acrescentar tags automaticamente em seus artigos'
279 admin:
280 title: 'Administração'
281 description: 'Como administrador você tem privilégios no wallabag. Você pode:'
282 new_user: 'Criar um novo usuário'
283 analytics: 'Configurar o analytics'
284 sharing: 'habilitar alguns parâmetros para compartilhamento de artigos'
285 export: 'Configurar exportação'
286 import: 'Configurar importação'
287 first_steps:
288 title: 'Primeiros passos'
289 description: "Agora o wallabag está bem configurado, é hora de arquivar a web. Você pode clicar no sinal de + no topo a direita para adicionar um link."
290 new_article: 'Salvar seu primeiro artigo'
291 unread_articles: 'E classificá-lo!'
292 migrate:
293 title: 'Migrar de um serviço existente'
294 description: 'Você está usando um outro serviço? Nós podemos ajudá-lo a recuperar seus dados para o wallabag.'
295 pocket: 'Migrar do Pocket'
296 wallabag_v1: 'Migrar do wallabag v1'
297 wallabag_v2: 'Migrar do wallabag v2'
298 readability: 'Migrate from Readability'
299 instapaper: 'Migrate from Instapaper'
300 developer:
301 title: 'Desenvolvedores'
302 description: 'Nós também agradecemos os desenvolvedores: Docker, API, traduções, etc.'
303 create_application: 'Criar sua aplicação terceira'
304 use_docker: 'Usar o Docker para instalar o wallabag'
305 docs:
306 title: 'Documentação completa'
307 description: "Existem muitas funcionalidades no wallabag. Não hesite em ler o manual para conhecê-las e aprender como usá-las."
308 annotate: 'Anotar seu artigo'
309 export: 'Converter seu artigo em ePUB ou PDF'
310 search_filters: 'veja coo você pode encontrar um artigo usanndo o motor de busca e filtros'
311 fetching_errors: 'O que eu posso fazer quando um artigo encontra erros na recuperação?'
312 all_docs: 'E outros muitos artigos!'
313 support:
314 title: 'Suporte'
315 description: 'Se você precisa de ajuda, nós estamos aqui.'
316 github: 'No GitHub'
317 email: 'Por e-mail'
318 gitter: 'No Gitter'
319
320tag:
321 page_title: 'Tags'
322 list:
323 number_on_the_page: '{0} Não existem tags.|{1} Uma tag.|]1,Inf[ Existem %count% tags.'
324 see_untagged_entries: 'Ver entradas sem tags'
325
326import:
327 page_title: 'Importar'
328 page_description: 'Bem-vindo ao importador do wallabag. Por favo selecione o serviço do qual deseja migrar.'
329 action:
330 import_contents: 'Importar conteúdos'
331 form:
332 mark_as_read_title: 'Marcar todos como lidos?'
333 mark_as_read_label: 'Marcar todas as entradas importadas como lidas'
334 file_label: 'Arquivo'
335 save_label: 'Carregar arquivo'
336 pocket:
337 page_title: 'Importar > Pocket'
338 description: 'Com este importador você importa todos os seus dados do Pocket. O Pocket não nos permite recuperar o conteúdo de seu serviço, então o conteúdo que pode ser lido é recarregado pelo wallabag.'
339 config_missing:
340 description: 'O importador do Pocket não está configurado.'
341 admin_message: 'Você precisa definir uma %keyurls%a pocket_consumer_key%keyurle%.'
342 user_message: 'Seu administrador do servidor precisa definir uma chave de API para o Pocket.'
343 authorize_message: 'Você pode importar seus dados de sua conta do Pocket. Você somente precisa clicar no botão abaixo e autorizar a aplicação a conectar-se ao getpocket.com.'
344 connect_to_pocket: 'Conecte ao Pocket e importe os dados'
345 wallabag_v1:
346 page_title: 'Importar > Wallabag v1'
347 description: 'Com este importador você importa todos os seus artigos do wallabag v1. Na sua página de configuração, clique em "JSON export" na opção "Export your wallabag data". Você irá criar um arquivo "wallabag-export-1-xxxx-xx-xx.json".'
348 how_to: 'Por favor, selecione seu exportador wallabag e clique no botão abaixo para carregar e importar.'
349 wallabag_v2:
350 page_title: 'Importar > Wallabag v2'
351 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".'
352 readability:
353 page_title: 'Importar > Readability'
354 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).'
355 how_to: 'Por favor, selecione sua exportação do Readability e clique no botão abaixo para importá-la.'
356 worker:
357 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 é:"
358 firefox:
359 page_title: 'Importar > Firefox'
360 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."
361 how_to: "Por favor, escolha o arquivo de backup dos favoritos e clique no botão abaixo para importá-lo. Note que o processo pode demorar até que todos os artigos tenham sido copiados."
362 chrome:
363 page_title: 'Importar > Chrome'
364 description: "Com este importador você importa todos os favoritos de seu Chrome. A localização do arquivo depende de seu sistema operacional: <ul><li>Em Linux, vá para o diretório <code>~/.config/chromium/Default/</code></li><li>Em Windows, ele deve estar em <code>%LOCALAPPDATA%\\Google\\Chrome\\User Data\\Default</code></li><li>Em OS X, ele deve estar em <code>~/Library/Application Support/Google/Chrome/Default/Bookmarks</code></li></ul>Uma vez que você pegou o arquivo, copie-o para algum lugar que você o encontre.<em><br>Note que se você possui o Chromium ao invés do Chrome, você precisa corrigir os caminhos.</em></p>"
365 how_to: "Por favor, escolha o arquivo de backup dos favoritos e clique no botão abaixo para importá-lo. Note que o processo pode demorar até que todos os artigos tenham sido copiados."
366 instapaper:
367 page_title: 'Importar > Instapaper'
368 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").'
369 how_to: 'Por favor, selecione sua exportação do seu Instapaper e clique no botão abaixo para importá-la.'
370
371developer:
372 page_title: 'Desenvolvedor'
373 welcome_message: 'Bem-vindo a API do wallabag'
374 documentation: 'Documentação'
375 how_to_first_app: 'Como criar minha primeira aplicação'
376 full_documentation: 'Ver a documentação completa da API'
377 list_methods: 'Lista de métodos da API'
378 clients:
379 title: 'Clientes'
380 create_new: 'Criar um novo cliente'
381 existing_clients:
382 title: 'Clientes existentes'
383 field_id: 'ID do cliente'
384 field_secret: 'Chave do cliente'
385 field_uris: 'URIs de redirecionamento'
386 field_grant_types: 'Tipo permitido'
387 no_client: 'Nenhum cliente até agora.'
388 remove:
389 warn_message_1: 'Você tem permissão pare remover este cliente. Esta ação é IRREVERSÍVEL !'
390 warn_message_2: 'Se você remover isso, todo o aplicativo configurado com este cliente não poderá se autenticar no seu wallabag.'
391 action: 'Remover este cliente'
392 client:
393 page_title: 'Desenvolvedor > Novo cliente'
394 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.'
395 form:
396 name_label: 'Nome do cliente'
397 redirect_uris_label: 'URIs de redirecionamento'
398 save_label: 'Criar um novo cliente'
399 action_back: 'Voltar'
400 client_parameter:
401 page_title: 'Desenvolvedor > Parâmetros de clientes'
402 page_description: 'Aqui estão os parâmetros de seus clientes.'
403 field_name: 'Nome do cliente'
404 field_id: 'ID do cliente'
405 field_secret: 'Chave do cliente'
406 back: 'Voltar'
407 read_howto: 'Leia o how-to "Criar minha primeira aplicação"'
408 howto:
409 page_title: 'Desenvolvedor > Criar minha primeira aplicação'
410 description:
411 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.'
412 paragraph_2: 'Você precisa de um token para a comunicação entre sua aplicação terceira e a API do wallabag.'
413 paragraph_3: 'Para criar este token, você precisa <a href="%link%">criar um novo cliente</a>.'
414 paragraph_4: 'Agora, crie seu token (altere client_id, client_secret, username e password com os valores corretos):'
415 paragraph_5: 'A API pode retornar uma resposta como essa:'
416 paragraph_6: 'O access_token é utilizável para fazer uma chamada para o endpoint da API. Por exemplo:'
417 paragraph_7: 'Esta chamada pode retornar todas as entradas de seu usuário.'
418 paragraph_8: 'Se você deseja ver todos os endpoints da API, dê uma olhada <a href="%link%">em nossa documentação da API</a>.'
419 back: 'Voltar'
420
421user:
422 page_title: 'Gerenciamento de Usuários'
423 new_user: 'Criar um novo usuário'
424 edit_user: 'Editar um usuário existente'
425 description: 'Aqui você gerencia todos os usuários (cria, edita e apaga)'
426 list:
427 actions: 'Ações'
428 edit_action: 'Editar'
429 yes: 'Sim'
430 no: 'Não'
431 create_new_one: 'Criar um novo usuário'
432 form:
433 username_label: 'Nome de Usuário'
434 name_label: 'Nome'
435 password_label: 'Senha'
436 repeat_new_password_label: 'Repita a nova senha'
437 plain_password_label: '????'
438 email_label: 'E-mail'
439 enabled_label: 'Habilitado'
440 locked_label: 'Travado'
441 last_login_label: 'Último login'
442 twofactor_label: 'Autenticação de dois passos'
443 save: 'Salvar'
444 delete: 'Apagar'
445 delete_confirm: 'Tem certeza?'
446 back_to_list: 'Voltar para a lista'
447
448flashes:
449 config:
450 notice:
451 config_saved: 'Configiração salva. Alguns parâmetros podem ser considerados depois da desconexão.'
452 password_updated: 'Senha atualizada'
453 password_not_updated_demo: 'Em modo de demonstração, você não pode alterar a senha deste usuário.'
454 rss_updated: 'Informação de RSS atualizada'
455 tagging_rules_updated: 'Regras de tags atualizadas'
456 tagging_rules_deleted: 'Regra de tag apagada'
457 rss_token_updated: 'Token RSS atualizado'
458 entry:
459 notice:
460 entry_already_saved: 'Entrada já foi salva em %date%'
461 entry_saved: 'Entrada salva'
462 entry_saved_failed: 'Failed to save entry'
463 entry_updated: 'Entrada atualizada'
464 entry_reloaded: 'Entrada recarregada'
465 entry_reloaded_failed: 'Falha em recarregar a entrada'
466 entry_archived: 'Entrada arquivada'
467 entry_unarchived: 'Entrada desarquivada'
468 entry_starred: 'Entrada destacada'
469 entry_unstarred: 'Entrada não destacada'
470 entry_deleted: 'Entrada apagada'
471 tag:
472 notice:
473 tag_added: 'Tag adicionada'
474 import:
475 notice:
476 failed: 'Importação falhou, por favor tente novamente.'
477 failed_on_file: 'Erro ao processar a importação. Por favor verifique seu arquivo de importação.'
478 summary: 'relatório de importação: %imported% importados, %skipped% já existem.'
479 summary_with_queue: 'Importar sumáario: %queued% agendados.'
480 error:
481 redis_enabled_not_installed: 'O Redis está habilitado para importação assíncrona mas parece que <u>não podemos nos conectar nele</u>. Por favor verifique as configurações do Redis.'
482 rabbit_enabled_not_installed: 'O RabbitMQ está habilitado para importação assíncrona mas parece que <u>não podemos nos conectar nele</u>. Por favor verifique as configurações do RabbitMQ.'
483 developer:
484 notice:
485 client_created: 'Novo cliente criado.'
486 client_deleted: 'Cliente removido'
487 user:
488 notice:
489 added: 'Usuário "%username%" adicionado'
490 updated: 'Usuário "%username%" atualizado'
491 deleted: 'Usuário "%username%" removido'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml
index 070abe27..50f1b6a2 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml
@@ -150,7 +150,7 @@ entry:
150 # starred: 'Starred entries' 150 # starred: 'Starred entries'
151 # archived: 'Archived entries' 151 # archived: 'Archived entries'
152 # filtered: 'Filtered entries' 152 # filtered: 'Filtered entries'
153 # filtered_tags: 'Filtered by tags' 153 # filtered_tags: 'Filtered by tags:'
154 # untagged: 'Untagged entries' 154 # untagged: 'Untagged entries'
155 list: 155 list:
156 # number_on_the_page: '{0} There is no entry.|{1} There is one entry.|]1,Inf[ There are %count% entries.' 156 # number_on_the_page: '{0} There is no entry.|{1} There is one entry.|]1,Inf[ There are %count% entries.'
@@ -472,7 +472,6 @@ flashes:
472 rss_updated: 'Informație RSS actualizată' 472 rss_updated: 'Informație RSS actualizată'
473 # tagging_rules_updated: 'Tagging rules updated' 473 # tagging_rules_updated: 'Tagging rules updated'
474 # tagging_rules_deleted: 'Tagging rule deleted' 474 # tagging_rules_deleted: 'Tagging rule deleted'
475 # user_added: 'User "%username%" added'
476 # rss_token_updated: 'RSS token updated' 475 # rss_token_updated: 'RSS token updated'
477 # annotations_reset: Annotations reset 476 # annotations_reset: Annotations reset
478 # tags_reset: Tags reset 477 # tags_reset: Tags reset
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml
index 7679b32a..07939ebc 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml
@@ -150,7 +150,7 @@ entry:
150 # starred: 'Starred entries' 150 # starred: 'Starred entries'
151 # archived: 'Archived entries' 151 # archived: 'Archived entries'
152 # filtered: 'Filtered entries' 152 # filtered: 'Filtered entries'
153 # filtered_tags: 'Filtered by tags' 153 # filtered_tags: 'Filtered by tags:'
154 # untagged: 'Untagged entries' 154 # untagged: 'Untagged entries'
155 list: 155 list:
156 number_on_the_page: '{0} Herhangi bir makale yok.|{1} Burada bir adet makale var.|]1,Inf[ Burada %count% adet makale var.' 156 number_on_the_page: '{0} Herhangi bir makale yok.|{1} Burada bir adet makale var.|]1,Inf[ Burada %count% adet makale var.'
@@ -472,7 +472,6 @@ flashes:
472 rss_updated: 'RSS bilgiler güncellendi' 472 rss_updated: 'RSS bilgiler güncellendi'
473 tagging_rules_updated: 'Tagging rules updated' 473 tagging_rules_updated: 'Tagging rules updated'
474 tagging_rules_deleted: 'Tagging rule deleted' 474 tagging_rules_deleted: 'Tagging rule deleted'
475 user_added: 'User "%username%" added'
476 rss_token_updated: 'RSS token updated' 475 rss_token_updated: 'RSS token updated'
477 # annotations_reset: Annotations reset 476 # annotations_reset: Annotations reset
478 # tags_reset: Tags reset 477 # tags_reset: Tags reset
diff --git a/src/Wallabag/CoreBundle/Resources/translations/validators.fr.yml b/src/Wallabag/CoreBundle/Resources/translations/validators.fr.yml
index 7ecb4acf..2d5eca29 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/validators.fr.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/validators.fr.yml
@@ -1,6 +1,6 @@
1validator: 1validator:
2 password_must_match: 'Les deux mots de passe doivent être les mêmes' 2 password_must_match: "Les deux mots de passe doivent être les mêmes"
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: "Ca ne va pas plaire à l'application" 5 item_per_page_too_high: "Ça ne va pas plaire à lapplication"
6 rss_limit_too_hight: "Ca ne va pas plaire à l'application" 6 rss_limit_too_hight: "Ça ne va pas plaire à lapplication"
diff --git a/src/Wallabag/CoreBundle/Resources/translations/validators.pt.yml b/src/Wallabag/CoreBundle/Resources/translations/validators.pt.yml
new file mode 100644
index 00000000..49890830
--- /dev/null
+++ b/src/Wallabag/CoreBundle/Resources/translations/validators.pt.yml
@@ -0,0 +1,6 @@
1validator:
2 password_must_match: 'Os campos de senha devem coincidir.'
3 password_too_short: 'A senha deve ter pelo menos 8 caracteres'
4 password_wrong_value: 'A senha atual informada está errada'
5 item_per_page_too_high: 'Certamente isso pode matar a aplicação'
6 rss_limit_too_hight: 'Certamente isso pode matar a aplicação'
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 3af88b23..5d657c7e 100644
--- a/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Entry/entries.html.twig
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Entry/entries.html.twig
@@ -1,7 +1,11 @@
1{% extends "WallabagCoreBundle::layout.html.twig" %} 1{% extends "WallabagCoreBundle::layout.html.twig" %}
2 2
3{% block title %} 3{% block title %}
4 {% include "@WallabagCore/themes/common/Entry/_title.html.twig" %} 4 {% set currentTag = '' %}
5 {% if tag is defined %}
6 {% set currentTag = tag %}
7 {% endif %}
8 {% include "@WallabagCore/themes/common/Entry/_title.html.twig" with {'currentTag': currentTag} %}
5{% endblock %} 9{% endblock %}
6 10
7{% block content %} 11{% block content %}
@@ -49,19 +53,23 @@
49 <!-- Export --> 53 <!-- Export -->
50 <aside id="download-form"> 54 <aside id="download-form">
51 {% set currentRoute = app.request.attributes.get('_route') %} 55 {% set currentRoute = app.request.attributes.get('_route') %}
56 {% set currentTag = '' %}
57 {% if tag is defined %}
58 {% set currentTag = tag %}
59 {% endif %}
52 {% if currentRoute == 'homepage' %} 60 {% if currentRoute == 'homepage' %}
53 {% set currentRoute = 'unread' %} 61 {% set currentRoute = 'unread' %}
54 {% endif %} 62 {% endif %}
55 <h2>{{ 'entry.list.export_title'|trans }}</h2> 63 <h2>{{ 'entry.list.export_title'|trans }}</h2>
56 <a href="javascript: void(null);" id="download-form-close" class="close-button--popup close-button">&times;</a> 64 <a href="javascript: void(null);" id="download-form-close" class="close-button--popup close-button">&times;</a>
57 <ul> 65 <ul>
58 {% if craue_setting('export_epub') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'epub' }) }}">EPUB</a></li>{% endif %} 66 {% 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 %}
59 {% if craue_setting('export_mobi') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'mobi' }) }}">MOBI</a></li>{% endif %} 67 {% 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 %}
60 {% if craue_setting('export_pdf') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'pdf' }) }}">PDF</a></li>{% endif %} 68 {% 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 %}
61 {% if craue_setting('export_csv') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'json' }) }}">JSON</a></li>{% endif %} 69 {% 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 %}
62 {% if craue_setting('export_json') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'csv' }) }}">CSV</a></li>{% endif %} 70 {% 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 %}
63 {% if craue_setting('export_txt') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'txt' }) }}">TXT</a></li>{% endif %} 71 {% 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 %}
64 {% if craue_setting('export_xml') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'xml' }) }}">XML</a></li>{% endif %} 72 {% 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 %}
65 </ul> 73 </ul>
66 </aside> 74 </aside>
67 75
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/common/Entry/_title.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/common/Entry/_title.html.twig
index d1c2f203..92cabdd9 100644
--- a/src/Wallabag/CoreBundle/Resources/views/themes/common/Entry/_title.html.twig
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/common/Entry/_title.html.twig
@@ -7,7 +7,7 @@
7{% elseif currentRoute == 'all' %} 7{% elseif currentRoute == 'all' %}
8 {{ 'entry.page_titles.filtered'|trans }} 8 {{ 'entry.page_titles.filtered'|trans }}
9{% elseif currentRoute == 'tag_entries' %} 9{% elseif currentRoute == 'tag_entries' %}
10 {{ 'entry.page_titles.filtered_tags'|trans }} 10 {{ 'entry.page_titles.filtered_tags'|trans }} {{ currentTag }}
11{% elseif currentRoute == 'untagged' %} 11{% elseif currentRoute == 'untagged' %}
12 {{ 'entry.page_titles.untagged'|trans }} 12 {{ 'entry.page_titles.untagged'|trans }}
13{% else %} 13{% else %}
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 919f94ec..1225e680 100644
--- a/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/entries.html.twig
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/entries.html.twig
@@ -1,7 +1,11 @@
1{% extends "WallabagCoreBundle::layout.html.twig" %} 1{% extends "WallabagCoreBundle::layout.html.twig" %}
2 2
3{% block title %} 3{% block title %}
4 {% include "@WallabagCore/themes/common/Entry/_title.html.twig" %} 4 {% set currentTag = '' %}
5 {% if tag is defined %}
6 {% set currentTag = tag %}
7 {% endif %}
8 {% include "@WallabagCore/themes/common/Entry/_title.html.twig" with {'currentTag': currentTag} %}
5{% endblock %} 9{% endblock %}
6 10
7{% block content %} 11{% block content %}
@@ -95,18 +99,22 @@
95 <!-- Export --> 99 <!-- Export -->
96 <div id="export" class="side-nav fixed right-aligned"> 100 <div id="export" class="side-nav fixed right-aligned">
97 {% set currentRoute = app.request.attributes.get('_route') %} 101 {% set currentRoute = app.request.attributes.get('_route') %}
102 {% set currentTag = '' %}
103 {% if tag is defined %}
104 {% set currentTag = tag %}
105 {% endif %}
98 {% if currentRoute == 'homepage' %} 106 {% if currentRoute == 'homepage' %}
99 {% set currentRoute = 'unread' %} 107 {% set currentRoute = 'unread' %}
100 {% endif %} 108 {% endif %}
101 <h4 class="center">{{ 'entry.list.export_title'|trans }}</h4> 109 <h4 class="center">{{ 'entry.list.export_title'|trans }}</h4>
102 <ul> 110 <ul>
103 {% if craue_setting('export_epub') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'epub' }) }}">EPUB</a></li>{% endif %} 111 {% 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 %}
104 {% if craue_setting('export_mobi') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'mobi' }) }}">MOBI</a></li>{% endif %} 112 {% 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 %}
105 {% if craue_setting('export_pdf') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'pdf' }) }}">PDF</a></li>{% endif %} 113 {% 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 %}
106 {% if craue_setting('export_csv') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'json' }) }}">JSON</a></li>{% endif %} 114 {% 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 %}
107 {% if craue_setting('export_json') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'csv' }) }}">CSV</a></li>{% endif %} 115 {% 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 %}
108 {% if craue_setting('export_txt') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'txt' }) }}">TXT</a></li>{% endif %} 116 {% 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 %}
109 {% if craue_setting('export_xml') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'xml' }) }}">XML</a></li>{% endif %} 117 {% 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 %}
110 </ul> 118 </ul>
111 </div> 119 </div>
112 120
diff --git a/src/Wallabag/ImportBundle/Command/ImportCommand.php b/src/Wallabag/ImportBundle/Command/ImportCommand.php
index 2f7a906e..13f3dcb9 100644
--- a/src/Wallabag/ImportBundle/Command/ImportCommand.php
+++ b/src/Wallabag/ImportBundle/Command/ImportCommand.php
@@ -56,6 +56,9 @@ class ImportCommand extends ContainerAwareCommand
56 case 'instapaper': 56 case 'instapaper':
57 $import = $this->getContainer()->get('wallabag_import.instapaper.import'); 57 $import = $this->getContainer()->get('wallabag_import.instapaper.import');
58 break; 58 break;
59 case 'instapaper':
60 $wallabag = $this->getContainer()->get('wallabag_import.instapaper.import');
61 break;
59 case 'v1': 62 case 'v1':
60 default: 63 default:
61 $import = $this->getContainer()->get('wallabag_import.wallabag_v1.import'); 64 $import = $this->getContainer()->get('wallabag_import.wallabag_v1.import');
diff --git a/src/Wallabag/UserBundle/Mailer/AuthCodeMailer.php b/src/Wallabag/UserBundle/Mailer/AuthCodeMailer.php
index ca9d18f1..961208f2 100644
--- a/src/Wallabag/UserBundle/Mailer/AuthCodeMailer.php
+++ b/src/Wallabag/UserBundle/Mailer/AuthCodeMailer.php
@@ -4,7 +4,6 @@ namespace Wallabag\UserBundle\Mailer;
4 4
5use Scheb\TwoFactorBundle\Model\Email\TwoFactorInterface; 5use Scheb\TwoFactorBundle\Model\Email\TwoFactorInterface;
6use Scheb\TwoFactorBundle\Mailer\AuthCodeMailerInterface; 6use Scheb\TwoFactorBundle\Mailer\AuthCodeMailerInterface;
7use Craue\ConfigBundle\Util\Config;
8 7
9/** 8/**
10 * Custom mailer for TwoFactorBundle email. 9 * Custom mailer for TwoFactorBundle email.
@@ -61,16 +60,17 @@ class AuthCodeMailer implements AuthCodeMailerInterface
61 * @param \Twig_Environment $twig 60 * @param \Twig_Environment $twig
62 * @param string $senderEmail 61 * @param string $senderEmail
63 * @param string $senderName 62 * @param string $senderName
64 * @param Config $craueConfig Craue\Config instance to get wallabag support url from database 63 * @param string $supportUrl wallabag support url
64 * @param string $wallabagUrl wallabag instance url
65 */ 65 */
66 public function __construct(\Swift_Mailer $mailer, \Twig_Environment $twig, $senderEmail, $senderName, Config $craueConfig) 66 public function __construct(\Swift_Mailer $mailer, \Twig_Environment $twig, $senderEmail, $senderName, $supportUrl, $wallabagUrl)
67 { 67 {
68 $this->mailer = $mailer; 68 $this->mailer = $mailer;
69 $this->twig = $twig; 69 $this->twig = $twig;
70 $this->senderEmail = $senderEmail; 70 $this->senderEmail = $senderEmail;
71 $this->senderName = $senderName; 71 $this->senderName = $senderName;
72 $this->supportUrl = $craueConfig->get('wallabag_support_url'); 72 $this->supportUrl = $supportUrl;
73 $this->wallabagUrl = $craueConfig->get('wallabag_url'); 73 $this->wallabagUrl = $wallabagUrl;
74 } 74 }
75 75
76 /** 76 /**
diff --git a/src/Wallabag/UserBundle/Resources/config/services.yml b/src/Wallabag/UserBundle/Resources/config/services.yml
index 8062e53f..164a25ec 100644
--- a/src/Wallabag/UserBundle/Resources/config/services.yml
+++ b/src/Wallabag/UserBundle/Resources/config/services.yml
@@ -6,7 +6,8 @@ services:
6 - "@twig" 6 - "@twig"
7 - "%scheb_two_factor.email.sender_email%" 7 - "%scheb_two_factor.email.sender_email%"
8 - "%scheb_two_factor.email.sender_name%" 8 - "%scheb_two_factor.email.sender_name%"
9 - "@craue_config" 9 - '@=service(''craue_config'').get(''wallabag_support_url'')'
10 - '@=service(''craue_config'').get(''wallabag_url'')'
10 11
11 wallabag_user.password_resetting: 12 wallabag_user.password_resetting:
12 class: Wallabag\UserBundle\EventListener\PasswordResettingListener 13 class: Wallabag\UserBundle\EventListener\PasswordResettingListener
diff --git a/src/Wallabag/UserBundle/Resources/translations/wallabag_user.fr.yml b/src/Wallabag/UserBundle/Resources/translations/wallabag_user.fr.yml
index 30ab5dd9..fbc95a05 100644
--- a/src/Wallabag/UserBundle/Resources/translations/wallabag_user.fr.yml
+++ b/src/Wallabag/UserBundle/Resources/translations/wallabag_user.fr.yml
@@ -1,11 +1,11 @@
1# Two factor mail 1# Two factor mail
2auth_code: 2auth_code:
3 on: 'sur' 3 on: "sur"
4 mailer: 4 mailer:
5 subject: "Code d'authentification wallabag" 5 subject: "Code dauthentification wallabag"
6 body: 6 body:
7 hello: "Bonjour %user%," 7 hello: "Bonjour %user%,"
8 first_para: "Comme vous avez activé la double authentification sur votre compte wallabag et que vous venez de vous connecter depuis un nouvel appareil (ordinateur, téléphone, etc.), nous vous envoyons un code pour valider votre connexion." 8 first_para: "Comme vous avez activé la double authentification sur votre compte wallabag et que vous venez de vous connecter depuis un nouvel appareil (ordinateur, téléphone, etc.), nous vous envoyons un code pour valider votre connexion."
9 second_para: "Voici le code à renseigner :" 9 second_para: "Voici le code à renseigner :"
10 support: "Si vous avez un problème de connexion, n'hésitez pas à contacter le support :" 10 support: "Si vous avez un problème de connexion, nhésitez pas à contacter le support :"
11 signature: "L'équipe wallabag" 11 signature: "Léquipe wallabag"
diff --git a/tests/Wallabag/ApiBundle/Controller/EntryRestControllerTest.php b/tests/Wallabag/ApiBundle/Controller/EntryRestControllerTest.php
new file mode 100644
index 00000000..566e9493
--- /dev/null
+++ b/tests/Wallabag/ApiBundle/Controller/EntryRestControllerTest.php
@@ -0,0 +1,681 @@
1<?php
2
3namespace Tests\Wallabag\ApiBundle\Controller;
4
5use Tests\Wallabag\ApiBundle\WallabagApiTestCase;
6use Wallabag\CoreBundle\Entity\Tag;
7
8class EntryRestControllerTest extends WallabagApiTestCase
9{
10 public function testGetOneEntry()
11 {
12 $entry = $this->client->getContainer()
13 ->get('doctrine.orm.entity_manager')
14 ->getRepository('WallabagCoreBundle:Entry')
15 ->findOneBy(['user' => 1, 'isArchived' => false]);
16
17 if (!$entry) {
18 $this->markTestSkipped('No content found in db.');
19 }
20
21 $this->client->request('GET', '/api/entries/'.$entry->getId().'.json');
22 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
23
24 $content = json_decode($this->client->getResponse()->getContent(), true);
25
26 $this->assertEquals($entry->getTitle(), $content['title']);
27 $this->assertEquals($entry->getUrl(), $content['url']);
28 $this->assertCount(count($entry->getTags()), $content['tags']);
29 $this->assertEquals($entry->getUserName(), $content['user_name']);
30 $this->assertEquals($entry->getUserEmail(), $content['user_email']);
31 $this->assertEquals($entry->getUserId(), $content['user_id']);
32
33 $this->assertEquals('application/json', $this->client->getResponse()->headers->get('Content-Type'));
34 }
35
36 public function testExportEntry()
37 {
38 $entry = $this->client->getContainer()
39 ->get('doctrine.orm.entity_manager')
40 ->getRepository('WallabagCoreBundle:Entry')
41 ->findOneBy(['user' => 1, 'isArchived' => false]);
42
43 if (!$entry) {
44 $this->markTestSkipped('No content found in db.');
45 }
46
47 $this->client->request('GET', '/api/entries/'.$entry->getId().'/export.epub');
48 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
49
50 // epub format got the content type in the content
51 $this->assertContains('application/epub', $this->client->getResponse()->getContent());
52 $this->assertEquals('application/epub+zip', $this->client->getResponse()->headers->get('Content-Type'));
53
54 // re-auth client for mobi
55 $client = $this->createAuthorizedClient();
56 $client->request('GET', '/api/entries/'.$entry->getId().'/export.mobi');
57 $this->assertEquals(200, $client->getResponse()->getStatusCode());
58
59 $this->assertEquals('application/x-mobipocket-ebook', $client->getResponse()->headers->get('Content-Type'));
60
61 // re-auth client for pdf
62 $client = $this->createAuthorizedClient();
63 $client->request('GET', '/api/entries/'.$entry->getId().'/export.pdf');
64 $this->assertEquals(200, $client->getResponse()->getStatusCode());
65
66 $this->assertContains('PDF-', $client->getResponse()->getContent());
67 $this->assertEquals('application/pdf', $client->getResponse()->headers->get('Content-Type'));
68
69 // re-auth client for pdf
70 $client = $this->createAuthorizedClient();
71 $client->request('GET', '/api/entries/'.$entry->getId().'/export.txt');
72 $this->assertEquals(200, $client->getResponse()->getStatusCode());
73
74 $this->assertContains('text/plain', $client->getResponse()->headers->get('Content-Type'));
75
76 // re-auth client for pdf
77 $client = $this->createAuthorizedClient();
78 $client->request('GET', '/api/entries/'.$entry->getId().'/export.csv');
79 $this->assertEquals(200, $client->getResponse()->getStatusCode());
80
81 $this->assertContains('application/csv', $client->getResponse()->headers->get('Content-Type'));
82 }
83
84 public function testGetOneEntryWrongUser()
85 {
86 $entry = $this->client->getContainer()
87 ->get('doctrine.orm.entity_manager')
88 ->getRepository('WallabagCoreBundle:Entry')
89 ->findOneBy(['user' => 2, 'isArchived' => false]);
90
91 if (!$entry) {
92 $this->markTestSkipped('No content found in db.');
93 }
94
95 $this->client->request('GET', '/api/entries/'.$entry->getId().'.json');
96
97 $this->assertEquals(403, $this->client->getResponse()->getStatusCode());
98 }
99
100 public function testGetEntries()
101 {
102 $this->client->request('GET', '/api/entries');
103
104 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
105
106 $content = json_decode($this->client->getResponse()->getContent(), true);
107
108 $this->assertGreaterThanOrEqual(1, count($content));
109 $this->assertNotEmpty($content['_embedded']['items']);
110 $this->assertGreaterThanOrEqual(1, $content['total']);
111 $this->assertEquals(1, $content['page']);
112 $this->assertGreaterThanOrEqual(1, $content['pages']);
113
114 $this->assertEquals('application/json', $this->client->getResponse()->headers->get('Content-Type'));
115 }
116
117 public function testGetEntriesWithFullOptions()
118 {
119 $this->client->request('GET', '/api/entries', [
120 'archive' => 1,
121 'starred' => 1,
122 'sort' => 'updated',
123 'order' => 'asc',
124 'page' => 1,
125 'perPage' => 2,
126 'tags' => 'foo',
127 'since' => 1443274283,
128 ]);
129
130 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
131
132 $content = json_decode($this->client->getResponse()->getContent(), true);
133
134 $this->assertGreaterThanOrEqual(1, count($content));
135 $this->assertArrayHasKey('items', $content['_embedded']);
136 $this->assertGreaterThanOrEqual(0, $content['total']);
137 $this->assertEquals(1, $content['page']);
138 $this->assertEquals(2, $content['limit']);
139 $this->assertGreaterThanOrEqual(1, $content['pages']);
140
141 $this->assertArrayHasKey('_links', $content);
142 $this->assertArrayHasKey('self', $content['_links']);
143 $this->assertArrayHasKey('first', $content['_links']);
144 $this->assertArrayHasKey('last', $content['_links']);
145
146 foreach (['self', 'first', 'last'] as $link) {
147 $this->assertArrayHasKey('href', $content['_links'][$link]);
148 $this->assertContains('archive=1', $content['_links'][$link]['href']);
149 $this->assertContains('starred=1', $content['_links'][$link]['href']);
150 $this->assertContains('sort=updated', $content['_links'][$link]['href']);
151 $this->assertContains('order=asc', $content['_links'][$link]['href']);
152 $this->assertContains('tags=foo', $content['_links'][$link]['href']);
153 $this->assertContains('since=1443274283', $content['_links'][$link]['href']);
154 }
155
156 $this->assertEquals('application/json', $this->client->getResponse()->headers->get('Content-Type'));
157 }
158
159 public function testGetStarredEntries()
160 {
161 $this->client->request('GET', '/api/entries', ['starred' => 1, 'sort' => 'updated']);
162
163 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
164
165 $content = json_decode($this->client->getResponse()->getContent(), true);
166
167 $this->assertGreaterThanOrEqual(1, count($content));
168 $this->assertNotEmpty($content['_embedded']['items']);
169 $this->assertGreaterThanOrEqual(1, $content['total']);
170 $this->assertEquals(1, $content['page']);
171 $this->assertGreaterThanOrEqual(1, $content['pages']);
172
173 $this->assertArrayHasKey('_links', $content);
174 $this->assertArrayHasKey('self', $content['_links']);
175 $this->assertArrayHasKey('first', $content['_links']);
176 $this->assertArrayHasKey('last', $content['_links']);
177
178 foreach (['self', 'first', 'last'] as $link) {
179 $this->assertArrayHasKey('href', $content['_links'][$link]);
180 $this->assertContains('starred=1', $content['_links'][$link]['href']);
181 $this->assertContains('sort=updated', $content['_links'][$link]['href']);
182 }
183
184 $this->assertEquals('application/json', $this->client->getResponse()->headers->get('Content-Type'));
185 }
186
187 public function testGetArchiveEntries()
188 {
189 $this->client->request('GET', '/api/entries', ['archive' => 1]);
190
191 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
192
193 $content = json_decode($this->client->getResponse()->getContent(), true);
194
195 $this->assertGreaterThanOrEqual(1, count($content));
196 $this->assertNotEmpty($content['_embedded']['items']);
197 $this->assertGreaterThanOrEqual(1, $content['total']);
198 $this->assertEquals(1, $content['page']);
199 $this->assertGreaterThanOrEqual(1, $content['pages']);
200
201 $this->assertArrayHasKey('_links', $content);
202 $this->assertArrayHasKey('self', $content['_links']);
203 $this->assertArrayHasKey('first', $content['_links']);
204 $this->assertArrayHasKey('last', $content['_links']);
205
206 foreach (['self', 'first', 'last'] as $link) {
207 $this->assertArrayHasKey('href', $content['_links'][$link]);
208 $this->assertContains('archive=1', $content['_links'][$link]['href']);
209 }
210
211 $this->assertEquals('application/json', $this->client->getResponse()->headers->get('Content-Type'));
212 }
213
214 public function testGetTaggedEntries()
215 {
216 $this->client->request('GET', '/api/entries', ['tags' => 'foo,bar']);
217
218 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
219
220 $content = json_decode($this->client->getResponse()->getContent(), true);
221
222 $this->assertGreaterThanOrEqual(1, count($content));
223 $this->assertNotEmpty($content['_embedded']['items']);
224 $this->assertGreaterThanOrEqual(1, $content['total']);
225 $this->assertEquals(1, $content['page']);
226 $this->assertGreaterThanOrEqual(1, $content['pages']);
227
228 $this->assertArrayHasKey('_links', $content);
229 $this->assertArrayHasKey('self', $content['_links']);
230 $this->assertArrayHasKey('first', $content['_links']);
231 $this->assertArrayHasKey('last', $content['_links']);
232
233 foreach (['self', 'first', 'last'] as $link) {
234 $this->assertArrayHasKey('href', $content['_links'][$link]);
235 $this->assertContains('tags='.urlencode('foo,bar'), $content['_links'][$link]['href']);
236 }
237
238 $this->assertEquals('application/json', $this->client->getResponse()->headers->get('Content-Type'));
239 }
240
241 public function testGetDatedEntries()
242 {
243 $this->client->request('GET', '/api/entries', ['since' => 1443274283]);
244
245 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
246
247 $content = json_decode($this->client->getResponse()->getContent(), true);
248
249 $this->assertGreaterThanOrEqual(1, count($content));
250 $this->assertNotEmpty($content['_embedded']['items']);
251 $this->assertGreaterThanOrEqual(1, $content['total']);
252 $this->assertEquals(1, $content['page']);
253 $this->assertGreaterThanOrEqual(1, $content['pages']);
254
255 $this->assertArrayHasKey('_links', $content);
256 $this->assertArrayHasKey('self', $content['_links']);
257 $this->assertArrayHasKey('first', $content['_links']);
258 $this->assertArrayHasKey('last', $content['_links']);
259
260 foreach (['self', 'first', 'last'] as $link) {
261 $this->assertArrayHasKey('href', $content['_links'][$link]);
262 $this->assertContains('since=1443274283', $content['_links'][$link]['href']);
263 }
264
265 $this->assertEquals('application/json', $this->client->getResponse()->headers->get('Content-Type'));
266 }
267
268 public function testGetDatedSupEntries()
269 {
270 $future = new \DateTime(date('Y-m-d H:i:s'));
271 $this->client->request('GET', '/api/entries', ['since' => $future->getTimestamp() + 1000]);
272
273 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
274
275 $content = json_decode($this->client->getResponse()->getContent(), true);
276
277 $this->assertGreaterThanOrEqual(1, count($content));
278 $this->assertEmpty($content['_embedded']['items']);
279 $this->assertEquals(0, $content['total']);
280 $this->assertEquals(1, $content['page']);
281 $this->assertEquals(1, $content['pages']);
282
283 $this->assertArrayHasKey('_links', $content);
284 $this->assertArrayHasKey('self', $content['_links']);
285 $this->assertArrayHasKey('first', $content['_links']);
286 $this->assertArrayHasKey('last', $content['_links']);
287
288 foreach (['self', 'first', 'last'] as $link) {
289 $this->assertArrayHasKey('href', $content['_links'][$link]);
290 $this->assertContains('since='.($future->getTimestamp() + 1000), $content['_links'][$link]['href']);
291 }
292
293 $this->assertEquals('application/json', $this->client->getResponse()->headers->get('Content-Type'));
294 }
295
296 public function testDeleteEntry()
297 {
298 $entry = $this->client->getContainer()
299 ->get('doctrine.orm.entity_manager')
300 ->getRepository('WallabagCoreBundle:Entry')
301 ->findOneByUser(1);
302
303 if (!$entry) {
304 $this->markTestSkipped('No content found in db.');
305 }
306
307 $this->client->request('DELETE', '/api/entries/'.$entry->getId().'.json');
308
309 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
310
311 $content = json_decode($this->client->getResponse()->getContent(), true);
312
313 $this->assertEquals($entry->getTitle(), $content['title']);
314 $this->assertEquals($entry->getUrl(), $content['url']);
315
316 // We'll try to delete this entry again
317 $this->client->request('DELETE', '/api/entries/'.$entry->getId().'.json');
318
319 $this->assertEquals(404, $this->client->getResponse()->getStatusCode());
320 }
321
322 public function testPostEntry()
323 {
324 $this->client->request('POST', '/api/entries.json', [
325 'url' => 'http://www.lemonde.fr/pixels/article/2015/03/28/plongee-dans-l-univers-d-ingress-le-jeu-de-google-aux-frontieres-du-reel_4601155_4408996.html',
326 'tags' => 'google',
327 'title' => 'New title for my article',
328 ]);
329
330 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
331
332 $content = json_decode($this->client->getResponse()->getContent(), true);
333
334 $this->assertGreaterThan(0, $content['id']);
335 $this->assertEquals('http://www.lemonde.fr/pixels/article/2015/03/28/plongee-dans-l-univers-d-ingress-le-jeu-de-google-aux-frontieres-du-reel_4601155_4408996.html', $content['url']);
336 $this->assertEquals(false, $content['is_archived']);
337 $this->assertEquals(false, $content['is_starred']);
338 $this->assertEquals('New title for my article', $content['title']);
339 $this->assertEquals(1, $content['user_id']);
340 $this->assertCount(1, $content['tags']);
341 }
342
343 public function testPostSameEntry()
344 {
345 $this->client->request('POST', '/api/entries.json', [
346 'url' => 'http://www.lemonde.fr/pixels/article/2015/03/28/plongee-dans-l-univers-d-ingress-le-jeu-de-google-aux-frontieres-du-reel_4601155_4408996.html',
347 'archive' => '1',
348 'tags' => 'google, apple',
349 ]);
350
351 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
352
353 $content = json_decode($this->client->getResponse()->getContent(), true);
354
355 $this->assertGreaterThan(0, $content['id']);
356 $this->assertEquals('http://www.lemonde.fr/pixels/article/2015/03/28/plongee-dans-l-univers-d-ingress-le-jeu-de-google-aux-frontieres-du-reel_4601155_4408996.html', $content['url']);
357 $this->assertEquals(true, $content['is_archived']);
358 $this->assertEquals(false, $content['is_starred']);
359 $this->assertCount(2, $content['tags']);
360 }
361
362 public function testPostArchivedAndStarredEntry()
363 {
364 $this->client->request('POST', '/api/entries.json', [
365 'url' => 'http://www.lemonde.fr/idees/article/2016/02/08/preserver-la-liberte-d-expression-sur-les-reseaux-sociaux_4861503_3232.html',
366 'archive' => '1',
367 'starred' => '1',
368 ]);
369
370 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
371
372 $content = json_decode($this->client->getResponse()->getContent(), true);
373
374 $this->assertGreaterThan(0, $content['id']);
375 $this->assertEquals('http://www.lemonde.fr/idees/article/2016/02/08/preserver-la-liberte-d-expression-sur-les-reseaux-sociaux_4861503_3232.html', $content['url']);
376 $this->assertEquals(true, $content['is_archived']);
377 $this->assertEquals(true, $content['is_starred']);
378 $this->assertEquals(1, $content['user_id']);
379 }
380
381 public function testPostArchivedAndStarredEntryWithoutQuotes()
382 {
383 $this->client->request('POST', '/api/entries.json', [
384 'url' => 'http://www.lemonde.fr/idees/article/2016/02/08/preserver-la-liberte-d-expression-sur-les-reseaux-sociaux_4861503_3232.html',
385 'archive' => 0,
386 'starred' => 1,
387 ]);
388
389 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
390
391 $content = json_decode($this->client->getResponse()->getContent(), true);
392
393 $this->assertGreaterThan(0, $content['id']);
394 $this->assertEquals('http://www.lemonde.fr/idees/article/2016/02/08/preserver-la-liberte-d-expression-sur-les-reseaux-sociaux_4861503_3232.html', $content['url']);
395 $this->assertEquals(false, $content['is_archived']);
396 $this->assertEquals(true, $content['is_starred']);
397 }
398
399 public function testPatchEntry()
400 {
401 $entry = $this->client->getContainer()
402 ->get('doctrine.orm.entity_manager')
403 ->getRepository('WallabagCoreBundle:Entry')
404 ->findOneByUser(1);
405
406 if (!$entry) {
407 $this->markTestSkipped('No content found in db.');
408 }
409
410 // hydrate the tags relations
411 $nbTags = count($entry->getTags());
412
413 $this->client->request('PATCH', '/api/entries/'.$entry->getId().'.json', [
414 'title' => 'New awesome title',
415 'tags' => 'new tag '.uniqid(),
416 'starred' => '1',
417 'archive' => '0',
418 ]);
419
420 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
421
422 $content = json_decode($this->client->getResponse()->getContent(), true);
423
424 $this->assertEquals($entry->getId(), $content['id']);
425 $this->assertEquals($entry->getUrl(), $content['url']);
426 $this->assertEquals('New awesome title', $content['title']);
427 $this->assertGreaterThan($nbTags, count($content['tags']));
428 $this->assertEquals(1, $content['user_id']);
429 }
430
431 public function testPatchEntryWithoutQuotes()
432 {
433 $entry = $this->client->getContainer()
434 ->get('doctrine.orm.entity_manager')
435 ->getRepository('WallabagCoreBundle:Entry')
436 ->findOneByUser(1);
437
438 if (!$entry) {
439 $this->markTestSkipped('No content found in db.');
440 }
441
442 // hydrate the tags relations
443 $nbTags = count($entry->getTags());
444
445 $this->client->request('PATCH', '/api/entries/'.$entry->getId().'.json', [
446 'title' => 'New awesome title',
447 'tags' => 'new tag '.uniqid(),
448 'starred' => 1,
449 'archive' => 0,
450 ]);
451
452 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
453
454 $content = json_decode($this->client->getResponse()->getContent(), true);
455
456 $this->assertEquals($entry->getId(), $content['id']);
457 $this->assertEquals($entry->getUrl(), $content['url']);
458 $this->assertEquals('New awesome title', $content['title']);
459 $this->assertGreaterThan($nbTags, count($content['tags']));
460 }
461
462 public function testGetTagsEntry()
463 {
464 $entry = $this->client->getContainer()
465 ->get('doctrine.orm.entity_manager')
466 ->getRepository('WallabagCoreBundle:Entry')
467 ->findOneWithTags($this->user->getId());
468
469 $entry = $entry[0];
470
471 if (!$entry) {
472 $this->markTestSkipped('No content found in db.');
473 }
474
475 $tags = [];
476 foreach ($entry->getTags() as $tag) {
477 $tags[] = ['id' => $tag->getId(), 'label' => $tag->getLabel(), 'slug' => $tag->getSlug()];
478 }
479
480 $this->client->request('GET', '/api/entries/'.$entry->getId().'/tags');
481
482 $this->assertEquals(json_encode($tags, JSON_HEX_QUOT), $this->client->getResponse()->getContent());
483 }
484
485 public function testPostTagsOnEntry()
486 {
487 $entry = $this->client->getContainer()
488 ->get('doctrine.orm.entity_manager')
489 ->getRepository('WallabagCoreBundle:Entry')
490 ->findOneByUser(1);
491
492 if (!$entry) {
493 $this->markTestSkipped('No content found in db.');
494 }
495
496 $nbTags = count($entry->getTags());
497
498 $newTags = 'tag1,tag2,tag3';
499
500 $this->client->request('POST', '/api/entries/'.$entry->getId().'/tags', ['tags' => $newTags]);
501
502 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
503
504 $content = json_decode($this->client->getResponse()->getContent(), true);
505
506 $this->assertArrayHasKey('tags', $content);
507 $this->assertEquals($nbTags + 3, count($content['tags']));
508
509 $entryDB = $this->client->getContainer()
510 ->get('doctrine.orm.entity_manager')
511 ->getRepository('WallabagCoreBundle:Entry')
512 ->find($entry->getId());
513
514 $tagsInDB = [];
515 foreach ($entryDB->getTags()->toArray() as $tag) {
516 $tagsInDB[$tag->getId()] = $tag->getLabel();
517 }
518
519 foreach (explode(',', $newTags) as $tag) {
520 $this->assertContains($tag, $tagsInDB);
521 }
522 }
523
524 public function testDeleteOneTagEntry()
525 {
526 $entry = $this->client->getContainer()
527 ->get('doctrine.orm.entity_manager')
528 ->getRepository('WallabagCoreBundle:Entry')
529 ->findOneWithTags($this->user->getId());
530 $entry = $entry[0];
531
532 if (!$entry) {
533 $this->markTestSkipped('No content found in db.');
534 }
535
536 // hydrate the tags relations
537 $nbTags = count($entry->getTags());
538 $tag = $entry->getTags()[0];
539
540 $this->client->request('DELETE', '/api/entries/'.$entry->getId().'/tags/'.$tag->getId().'.json');
541
542 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
543
544 $content = json_decode($this->client->getResponse()->getContent(), true);
545
546 $this->assertArrayHasKey('tags', $content);
547 $this->assertEquals($nbTags - 1, count($content['tags']));
548 }
549
550 public function testSaveIsArchivedAfterPost()
551 {
552 $entry = $this->client->getContainer()
553 ->get('doctrine.orm.entity_manager')
554 ->getRepository('WallabagCoreBundle:Entry')
555 ->findOneBy(['user' => 1, 'isArchived' => true]);
556
557 if (!$entry) {
558 $this->markTestSkipped('No content found in db.');
559 }
560
561 $this->client->request('POST', '/api/entries.json', [
562 'url' => $entry->getUrl(),
563 ]);
564
565 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
566
567 $content = json_decode($this->client->getResponse()->getContent(), true);
568
569 $this->assertEquals(true, $content['is_archived']);
570 }
571
572 public function testSaveIsStarredAfterPost()
573 {
574 $entry = $this->client->getContainer()
575 ->get('doctrine.orm.entity_manager')
576 ->getRepository('WallabagCoreBundle:Entry')
577 ->findOneBy(['user' => 1, 'isStarred' => true]);
578
579 if (!$entry) {
580 $this->markTestSkipped('No content found in db.');
581 }
582
583 $this->client->request('POST', '/api/entries.json', [
584 'url' => $entry->getUrl(),
585 ]);
586
587 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
588
589 $content = json_decode($this->client->getResponse()->getContent(), true);
590
591 $this->assertEquals(true, $content['is_starred']);
592 }
593
594 public function testSaveIsArchivedAfterPatch()
595 {
596 $entry = $this->client->getContainer()
597 ->get('doctrine.orm.entity_manager')
598 ->getRepository('WallabagCoreBundle:Entry')
599 ->findOneBy(['user' => 1, 'isArchived' => true]);
600
601 if (!$entry) {
602 $this->markTestSkipped('No content found in db.');
603 }
604
605 $this->client->request('PATCH', '/api/entries/'.$entry->getId().'.json', [
606 'title' => $entry->getTitle().'++',
607 ]);
608
609 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
610
611 $content = json_decode($this->client->getResponse()->getContent(), true);
612
613 $this->assertEquals(true, $content['is_archived']);
614 }
615
616 public function testSaveIsStarredAfterPatch()
617 {
618 $entry = $this->client->getContainer()
619 ->get('doctrine.orm.entity_manager')
620 ->getRepository('WallabagCoreBundle:Entry')
621 ->findOneBy(['user' => 1, 'isStarred' => true]);
622
623 if (!$entry) {
624 $this->markTestSkipped('No content found in db.');
625 }
626 $this->client->request('PATCH', '/api/entries/'.$entry->getId().'.json', [
627 'title' => $entry->getTitle().'++',
628 ]);
629
630 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
631
632 $content = json_decode($this->client->getResponse()->getContent(), true);
633
634 $this->assertEquals(true, $content['is_starred']);
635 }
636
637 public function testGetEntriesExists()
638 {
639 $this->client->request('GET', '/api/entries/exists?url=http://0.0.0.0/entry2');
640
641 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
642
643 $content = json_decode($this->client->getResponse()->getContent(), true);
644
645 $this->assertEquals(true, $content['exists']);
646 }
647
648 public function testGetEntriesExistsWithManyUrls()
649 {
650 $url1 = 'http://0.0.0.0/entry2';
651 $url2 = 'http://0.0.0.0/entry10';
652 $this->client->request('GET', '/api/entries/exists?urls[]='.$url1.'&urls[]='.$url2);
653
654 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
655
656 $content = json_decode($this->client->getResponse()->getContent(), true);
657
658 $this->assertArrayHasKey($url1, $content);
659 $this->assertArrayHasKey($url2, $content);
660 $this->assertEquals(true, $content[$url1]);
661 $this->assertEquals(false, $content[$url2]);
662 }
663
664 public function testGetEntriesExistsWhichDoesNotExists()
665 {
666 $this->client->request('GET', '/api/entries/exists?url=http://google.com/entry2');
667
668 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
669
670 $content = json_decode($this->client->getResponse()->getContent(), true);
671
672 $this->assertEquals(false, $content['exists']);
673 }
674
675 public function testGetEntriesExistsWithNoUrl()
676 {
677 $this->client->request('GET', '/api/entries/exists?url=');
678
679 $this->assertEquals(403, $this->client->getResponse()->getStatusCode());
680 }
681}
diff --git a/tests/Wallabag/ApiBundle/Controller/TagRestControllerTest.php b/tests/Wallabag/ApiBundle/Controller/TagRestControllerTest.php
new file mode 100644
index 00000000..bde5251f
--- /dev/null
+++ b/tests/Wallabag/ApiBundle/Controller/TagRestControllerTest.php
@@ -0,0 +1,162 @@
1<?php
2
3namespace Tests\Wallabag\ApiBundle\Controller;
4
5use Tests\Wallabag\ApiBundle\WallabagApiTestCase;
6use Wallabag\CoreBundle\Entity\Tag;
7
8class TagRestControllerTest extends WallabagApiTestCase
9{
10 public function testGetUserTags()
11 {
12 $this->client->request('GET', '/api/tags.json');
13
14 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
15
16 $content = json_decode($this->client->getResponse()->getContent(), true);
17
18 $this->assertGreaterThan(0, $content);
19 $this->assertArrayHasKey('id', $content[0]);
20 $this->assertArrayHasKey('label', $content[0]);
21
22 return end($content);
23 }
24
25 /**
26 * @depends testGetUserTags
27 */
28 public function testDeleteUserTag($tag)
29 {
30 $tagName = $tag['label'];
31
32 $this->client->request('DELETE', '/api/tags/'.$tag['id'].'.json');
33
34 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
35
36 $content = json_decode($this->client->getResponse()->getContent(), true);
37
38 $this->assertArrayHasKey('label', $content);
39 $this->assertEquals($tag['label'], $content['label']);
40 $this->assertEquals($tag['slug'], $content['slug']);
41
42 $entries = $this->client->getContainer()
43 ->get('doctrine.orm.entity_manager')
44 ->getRepository('WallabagCoreBundle:Entry')
45 ->findAllByTagId($this->user->getId(), $tag['id']);
46
47 $this->assertCount(0, $entries);
48
49 $tag = $this->client->getContainer()
50 ->get('doctrine.orm.entity_manager')
51 ->getRepository('WallabagCoreBundle:Tag')
52 ->findOneByLabel($tagName);
53
54 $this->assertNull($tag, $tagName.' was removed because it begun an orphan tag');
55 }
56
57 public function testDeleteTagByLabel()
58 {
59 $em = $this->client->getContainer()->get('doctrine.orm.entity_manager');
60 $entry = $this->client->getContainer()
61 ->get('doctrine.orm.entity_manager')
62 ->getRepository('WallabagCoreBundle:Entry')
63 ->findOneWithTags($this->user->getId());
64
65 $entry = $entry[0];
66
67 $tag = new Tag();
68 $tag->setLabel('Awesome tag for test');
69 $em->persist($tag);
70
71 $entry->addTag($tag);
72
73 $em->persist($entry);
74 $em->flush();
75
76 $this->client->request('DELETE', '/api/tag/label.json', ['tag' => $tag->getLabel()]);
77
78 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
79
80 $content = json_decode($this->client->getResponse()->getContent(), true);
81
82 $this->assertArrayHasKey('label', $content);
83 $this->assertEquals($tag->getLabel(), $content['label']);
84 $this->assertEquals($tag->getSlug(), $content['slug']);
85
86 $entries = $this->client->getContainer()
87 ->get('doctrine.orm.entity_manager')
88 ->getRepository('WallabagCoreBundle:Entry')
89 ->findAllByTagId($this->user->getId(), $tag->getId());
90
91 $this->assertCount(0, $entries);
92 }
93
94 public function testDeleteTagByLabelNotFound()
95 {
96 $this->client->request('DELETE', '/api/tag/label.json', ['tag' => 'does not exist']);
97
98 $this->assertEquals(404, $this->client->getResponse()->getStatusCode());
99 }
100
101 public function testDeleteTagsByLabel()
102 {
103 $em = $this->client->getContainer()->get('doctrine.orm.entity_manager');
104 $entry = $this->client->getContainer()
105 ->get('doctrine.orm.entity_manager')
106 ->getRepository('WallabagCoreBundle:Entry')
107 ->findOneWithTags($this->user->getId());
108
109 $entry = $entry[0];
110
111 $tag = new Tag();
112 $tag->setLabel('Awesome tag for tagsLabel');
113 $em->persist($tag);
114
115 $tag2 = new Tag();
116 $tag2->setLabel('Awesome tag for tagsLabel 2');
117 $em->persist($tag2);
118
119 $entry->addTag($tag);
120 $entry->addTag($tag2);
121
122 $em->persist($entry);
123 $em->flush();
124
125 $this->client->request('DELETE', '/api/tags/label.json', ['tags' => $tag->getLabel().','.$tag2->getLabel()]);
126
127 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
128
129 $content = json_decode($this->client->getResponse()->getContent(), true);
130
131 $this->assertCount(2, $content);
132
133 $this->assertArrayHasKey('label', $content[0]);
134 $this->assertEquals($tag->getLabel(), $content[0]['label']);
135 $this->assertEquals($tag->getSlug(), $content[0]['slug']);
136
137 $this->assertArrayHasKey('label', $content[1]);
138 $this->assertEquals($tag2->getLabel(), $content[1]['label']);
139 $this->assertEquals($tag2->getSlug(), $content[1]['slug']);
140
141 $entries = $this->client->getContainer()
142 ->get('doctrine.orm.entity_manager')
143 ->getRepository('WallabagCoreBundle:Entry')
144 ->findAllByTagId($this->user->getId(), $tag->getId());
145
146 $this->assertCount(0, $entries);
147
148 $entries = $this->client->getContainer()
149 ->get('doctrine.orm.entity_manager')
150 ->getRepository('WallabagCoreBundle:Entry')
151 ->findAllByTagId($this->user->getId(), $tag2->getId());
152
153 $this->assertCount(0, $entries);
154 }
155
156 public function testDeleteTagsByLabelNotFound()
157 {
158 $this->client->request('DELETE', '/api/tags/label.json', ['tags' => 'does not exist']);
159
160 $this->assertEquals(404, $this->client->getResponse()->getStatusCode());
161 }
162}
diff --git a/tests/Wallabag/ApiBundle/Controller/WallabagRestControllerTest.php b/tests/Wallabag/ApiBundle/Controller/WallabagRestControllerTest.php
index 6bca3c8b..c87e58de 100644
--- a/tests/Wallabag/ApiBundle/Controller/WallabagRestControllerTest.php
+++ b/tests/Wallabag/ApiBundle/Controller/WallabagRestControllerTest.php
@@ -3,705 +3,9 @@
3namespace Tests\Wallabag\ApiBundle\Controller; 3namespace Tests\Wallabag\ApiBundle\Controller;
4 4
5use Tests\Wallabag\ApiBundle\WallabagApiTestCase; 5use Tests\Wallabag\ApiBundle\WallabagApiTestCase;
6use Wallabag\CoreBundle\Entity\Tag;
7 6
8class WallabagRestControllerTest extends WallabagApiTestCase 7class WallabagRestControllerTest extends WallabagApiTestCase
9{ 8{
10 protected static $salt;
11
12 public function testGetOneEntry()
13 {
14 $entry = $this->client->getContainer()
15 ->get('doctrine.orm.entity_manager')
16 ->getRepository('WallabagCoreBundle:Entry')
17 ->findOneBy(['user' => 1, 'isArchived' => false]);
18
19 if (!$entry) {
20 $this->markTestSkipped('No content found in db.');
21 }
22
23 $this->client->request('GET', '/api/entries/'.$entry->getId().'.json');
24 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
25
26 $content = json_decode($this->client->getResponse()->getContent(), true);
27
28 $this->assertEquals($entry->getTitle(), $content['title']);
29 $this->assertEquals($entry->getUrl(), $content['url']);
30 $this->assertCount(count($entry->getTags()), $content['tags']);
31 $this->assertEquals($entry->getUserName(), $content['user_name']);
32 $this->assertEquals($entry->getUserEmail(), $content['user_email']);
33 $this->assertEquals($entry->getUserId(), $content['user_id']);
34
35 $this->assertEquals('application/json', $this->client->getResponse()->headers->get('Content-Type'));
36 }
37
38 public function testExportEntry()
39 {
40 $entry = $this->client->getContainer()
41 ->get('doctrine.orm.entity_manager')
42 ->getRepository('WallabagCoreBundle:Entry')
43 ->findOneBy(['user' => 1, 'isArchived' => false]);
44
45 if (!$entry) {
46 $this->markTestSkipped('No content found in db.');
47 }
48
49 $this->client->request('GET', '/api/entries/'.$entry->getId().'/export.epub');
50 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
51
52 // epub format got the content type in the content
53 $this->assertContains('application/epub', $this->client->getResponse()->getContent());
54 $this->assertEquals('application/epub+zip', $this->client->getResponse()->headers->get('Content-Type'));
55
56 // re-auth client for mobi
57 $client = $this->createAuthorizedClient();
58 $client->request('GET', '/api/entries/'.$entry->getId().'/export.mobi');
59 $this->assertEquals(200, $client->getResponse()->getStatusCode());
60
61 $this->assertEquals('application/x-mobipocket-ebook', $client->getResponse()->headers->get('Content-Type'));
62
63 // re-auth client for pdf
64 $client = $this->createAuthorizedClient();
65 $client->request('GET', '/api/entries/'.$entry->getId().'/export.pdf');
66 $this->assertEquals(200, $client->getResponse()->getStatusCode());
67
68 $this->assertContains('PDF-', $client->getResponse()->getContent());
69 $this->assertEquals('application/pdf', $client->getResponse()->headers->get('Content-Type'));
70
71 // re-auth client for pdf
72 $client = $this->createAuthorizedClient();
73 $client->request('GET', '/api/entries/'.$entry->getId().'/export.txt');
74 $this->assertEquals(200, $client->getResponse()->getStatusCode());
75
76 $this->assertContains('text/plain', $client->getResponse()->headers->get('Content-Type'));
77
78 // re-auth client for pdf
79 $client = $this->createAuthorizedClient();
80 $client->request('GET', '/api/entries/'.$entry->getId().'/export.csv');
81 $this->assertEquals(200, $client->getResponse()->getStatusCode());
82
83 $this->assertContains('application/csv', $client->getResponse()->headers->get('Content-Type'));
84 }
85
86 public function testGetOneEntryWrongUser()
87 {
88 $entry = $this->client->getContainer()
89 ->get('doctrine.orm.entity_manager')
90 ->getRepository('WallabagCoreBundle:Entry')
91 ->findOneBy(['user' => 2, 'isArchived' => false]);
92
93 if (!$entry) {
94 $this->markTestSkipped('No content found in db.');
95 }
96
97 $this->client->request('GET', '/api/entries/'.$entry->getId().'.json');
98
99 $this->assertEquals(403, $this->client->getResponse()->getStatusCode());
100 }
101
102 public function testGetEntries()
103 {
104 $this->client->request('GET', '/api/entries');
105
106 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
107
108 $content = json_decode($this->client->getResponse()->getContent(), true);
109
110 $this->assertGreaterThanOrEqual(1, count($content));
111 $this->assertNotEmpty($content['_embedded']['items']);
112 $this->assertGreaterThanOrEqual(1, $content['total']);
113 $this->assertEquals(1, $content['page']);
114 $this->assertGreaterThanOrEqual(1, $content['pages']);
115
116 $this->assertEquals('application/json', $this->client->getResponse()->headers->get('Content-Type'));
117 }
118
119 public function testGetEntriesWithFullOptions()
120 {
121 $this->client->request('GET', '/api/entries', [
122 'archive' => 1,
123 'starred' => 1,
124 'sort' => 'updated',
125 'order' => 'asc',
126 'page' => 1,
127 'perPage' => 2,
128 'tags' => 'foo',
129 'since' => 1443274283,
130 ]);
131
132 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
133
134 $content = json_decode($this->client->getResponse()->getContent(), true);
135
136 $this->assertGreaterThanOrEqual(1, count($content));
137 $this->assertArrayHasKey('items', $content['_embedded']);
138 $this->assertGreaterThanOrEqual(0, $content['total']);
139 $this->assertEquals(1, $content['page']);
140 $this->assertEquals(2, $content['limit']);
141 $this->assertGreaterThanOrEqual(1, $content['pages']);
142
143 $this->assertArrayHasKey('_links', $content);
144 $this->assertArrayHasKey('self', $content['_links']);
145 $this->assertArrayHasKey('first', $content['_links']);
146 $this->assertArrayHasKey('last', $content['_links']);
147
148 foreach (['self', 'first', 'last'] as $link) {
149 $this->assertArrayHasKey('href', $content['_links'][$link]);
150 $this->assertContains('archive=1', $content['_links'][$link]['href']);
151 $this->assertContains('starred=1', $content['_links'][$link]['href']);
152 $this->assertContains('sort=updated', $content['_links'][$link]['href']);
153 $this->assertContains('order=asc', $content['_links'][$link]['href']);
154 $this->assertContains('tags=foo', $content['_links'][$link]['href']);
155 $this->assertContains('since=1443274283', $content['_links'][$link]['href']);
156 }
157
158 $this->assertEquals('application/json', $this->client->getResponse()->headers->get('Content-Type'));
159 }
160
161 public function testGetStarredEntries()
162 {
163 $this->client->request('GET', '/api/entries', ['starred' => 1, 'sort' => 'updated']);
164
165 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
166
167 $content = json_decode($this->client->getResponse()->getContent(), true);
168
169 $this->assertGreaterThanOrEqual(1, count($content));
170 $this->assertNotEmpty($content['_embedded']['items']);
171 $this->assertGreaterThanOrEqual(1, $content['total']);
172 $this->assertEquals(1, $content['page']);
173 $this->assertGreaterThanOrEqual(1, $content['pages']);
174
175 $this->assertArrayHasKey('_links', $content);
176 $this->assertArrayHasKey('self', $content['_links']);
177 $this->assertArrayHasKey('first', $content['_links']);
178 $this->assertArrayHasKey('last', $content['_links']);
179
180 foreach (['self', 'first', 'last'] as $link) {
181 $this->assertArrayHasKey('href', $content['_links'][$link]);
182 $this->assertContains('starred=1', $content['_links'][$link]['href']);
183 $this->assertContains('sort=updated', $content['_links'][$link]['href']);
184 }
185
186 $this->assertEquals('application/json', $this->client->getResponse()->headers->get('Content-Type'));
187 }
188
189 public function testGetArchiveEntries()
190 {
191 $this->client->request('GET', '/api/entries', ['archive' => 1]);
192
193 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
194
195 $content = json_decode($this->client->getResponse()->getContent(), true);
196
197 $this->assertGreaterThanOrEqual(1, count($content));
198 $this->assertNotEmpty($content['_embedded']['items']);
199 $this->assertGreaterThanOrEqual(1, $content['total']);
200 $this->assertEquals(1, $content['page']);
201 $this->assertGreaterThanOrEqual(1, $content['pages']);
202
203 $this->assertArrayHasKey('_links', $content);
204 $this->assertArrayHasKey('self', $content['_links']);
205 $this->assertArrayHasKey('first', $content['_links']);
206 $this->assertArrayHasKey('last', $content['_links']);
207
208 foreach (['self', 'first', 'last'] as $link) {
209 $this->assertArrayHasKey('href', $content['_links'][$link]);
210 $this->assertContains('archive=1', $content['_links'][$link]['href']);
211 }
212
213 $this->assertEquals('application/json', $this->client->getResponse()->headers->get('Content-Type'));
214 }
215
216 public function testGetTaggedEntries()
217 {
218 $this->client->request('GET', '/api/entries', ['tags' => 'foo,bar']);
219
220 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
221
222 $content = json_decode($this->client->getResponse()->getContent(), true);
223
224 $this->assertGreaterThanOrEqual(1, count($content));
225 $this->assertNotEmpty($content['_embedded']['items']);
226 $this->assertGreaterThanOrEqual(1, $content['total']);
227 $this->assertEquals(1, $content['page']);
228 $this->assertGreaterThanOrEqual(1, $content['pages']);
229
230 $this->assertArrayHasKey('_links', $content);
231 $this->assertArrayHasKey('self', $content['_links']);
232 $this->assertArrayHasKey('first', $content['_links']);
233 $this->assertArrayHasKey('last', $content['_links']);
234
235 foreach (['self', 'first', 'last'] as $link) {
236 $this->assertArrayHasKey('href', $content['_links'][$link]);
237 $this->assertContains('tags='.urlencode('foo,bar'), $content['_links'][$link]['href']);
238 }
239
240 $this->assertEquals('application/json', $this->client->getResponse()->headers->get('Content-Type'));
241 }
242
243 public function testGetDatedEntries()
244 {
245 $this->client->request('GET', '/api/entries', ['since' => 1443274283]);
246
247 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
248
249 $content = json_decode($this->client->getResponse()->getContent(), true);
250
251 $this->assertGreaterThanOrEqual(1, count($content));
252 $this->assertNotEmpty($content['_embedded']['items']);
253 $this->assertGreaterThanOrEqual(1, $content['total']);
254 $this->assertEquals(1, $content['page']);
255 $this->assertGreaterThanOrEqual(1, $content['pages']);
256
257 $this->assertArrayHasKey('_links', $content);
258 $this->assertArrayHasKey('self', $content['_links']);
259 $this->assertArrayHasKey('first', $content['_links']);
260 $this->assertArrayHasKey('last', $content['_links']);
261
262 foreach (['self', 'first', 'last'] as $link) {
263 $this->assertArrayHasKey('href', $content['_links'][$link]);
264 $this->assertContains('since=1443274283', $content['_links'][$link]['href']);
265 }
266
267 $this->assertEquals('application/json', $this->client->getResponse()->headers->get('Content-Type'));
268 }
269
270 public function testGetDatedSupEntries()
271 {
272 $future = new \DateTime(date('Y-m-d H:i:s'));
273 $this->client->request('GET', '/api/entries', ['since' => $future->getTimestamp() + 1000]);
274
275 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
276
277 $content = json_decode($this->client->getResponse()->getContent(), true);
278
279 $this->assertGreaterThanOrEqual(1, count($content));
280 $this->assertEmpty($content['_embedded']['items']);
281 $this->assertEquals(0, $content['total']);
282 $this->assertEquals(1, $content['page']);
283 $this->assertEquals(1, $content['pages']);
284
285 $this->assertArrayHasKey('_links', $content);
286 $this->assertArrayHasKey('self', $content['_links']);
287 $this->assertArrayHasKey('first', $content['_links']);
288 $this->assertArrayHasKey('last', $content['_links']);
289
290 foreach (['self', 'first', 'last'] as $link) {
291 $this->assertArrayHasKey('href', $content['_links'][$link]);
292 $this->assertContains('since='.($future->getTimestamp() + 1000), $content['_links'][$link]['href']);
293 }
294
295 $this->assertEquals('application/json', $this->client->getResponse()->headers->get('Content-Type'));
296 }
297
298 public function testDeleteEntry()
299 {
300 $entry = $this->client->getContainer()
301 ->get('doctrine.orm.entity_manager')
302 ->getRepository('WallabagCoreBundle:Entry')
303 ->findOneByUser(1);
304
305 if (!$entry) {
306 $this->markTestSkipped('No content found in db.');
307 }
308
309 $this->client->request('DELETE', '/api/entries/'.$entry->getId().'.json');
310
311 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
312
313 $content = json_decode($this->client->getResponse()->getContent(), true);
314
315 $this->assertEquals($entry->getTitle(), $content['title']);
316 $this->assertEquals($entry->getUrl(), $content['url']);
317
318 // We'll try to delete this entry again
319 $this->client->request('DELETE', '/api/entries/'.$entry->getId().'.json');
320
321 $this->assertEquals(404, $this->client->getResponse()->getStatusCode());
322 }
323
324 public function testPostEntry()
325 {
326 $this->client->request('POST', '/api/entries.json', [
327 'url' => 'http://www.lemonde.fr/pixels/article/2015/03/28/plongee-dans-l-univers-d-ingress-le-jeu-de-google-aux-frontieres-du-reel_4601155_4408996.html',
328 'tags' => 'google',
329 'title' => 'New title for my article',
330 ]);
331
332 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
333
334 $content = json_decode($this->client->getResponse()->getContent(), true);
335
336 $this->assertGreaterThan(0, $content['id']);
337 $this->assertEquals('http://www.lemonde.fr/pixels/article/2015/03/28/plongee-dans-l-univers-d-ingress-le-jeu-de-google-aux-frontieres-du-reel_4601155_4408996.html', $content['url']);
338 $this->assertEquals(false, $content['is_archived']);
339 $this->assertEquals(false, $content['is_starred']);
340 $this->assertEquals('New title for my article', $content['title']);
341 $this->assertEquals(1, $content['user_id']);
342 $this->assertCount(1, $content['tags']);
343 }
344
345 public function testPostSameEntry()
346 {
347 $this->client->request('POST', '/api/entries.json', [
348 'url' => 'http://www.lemonde.fr/pixels/article/2015/03/28/plongee-dans-l-univers-d-ingress-le-jeu-de-google-aux-frontieres-du-reel_4601155_4408996.html',
349 'archive' => '1',
350 'tags' => 'google, apple',
351 ]);
352
353 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
354
355 $content = json_decode($this->client->getResponse()->getContent(), true);
356
357 $this->assertGreaterThan(0, $content['id']);
358 $this->assertEquals('http://www.lemonde.fr/pixels/article/2015/03/28/plongee-dans-l-univers-d-ingress-le-jeu-de-google-aux-frontieres-du-reel_4601155_4408996.html', $content['url']);
359 $this->assertEquals(true, $content['is_archived']);
360 $this->assertEquals(false, $content['is_starred']);
361 $this->assertCount(2, $content['tags']);
362 }
363
364 public function testPostArchivedAndStarredEntry()
365 {
366 $this->client->request('POST', '/api/entries.json', [
367 'url' => 'http://www.lemonde.fr/idees/article/2016/02/08/preserver-la-liberte-d-expression-sur-les-reseaux-sociaux_4861503_3232.html',
368 'archive' => '1',
369 'starred' => '1',
370 ]);
371
372 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
373
374 $content = json_decode($this->client->getResponse()->getContent(), true);
375
376 $this->assertGreaterThan(0, $content['id']);
377 $this->assertEquals('http://www.lemonde.fr/idees/article/2016/02/08/preserver-la-liberte-d-expression-sur-les-reseaux-sociaux_4861503_3232.html', $content['url']);
378 $this->assertEquals(true, $content['is_archived']);
379 $this->assertEquals(true, $content['is_starred']);
380 $this->assertEquals(1, $content['user_id']);
381 }
382
383 public function testPostArchivedAndStarredEntryWithoutQuotes()
384 {
385 $this->client->request('POST', '/api/entries.json', [
386 'url' => 'http://www.lemonde.fr/idees/article/2016/02/08/preserver-la-liberte-d-expression-sur-les-reseaux-sociaux_4861503_3232.html',
387 'archive' => 0,
388 'starred' => 1,
389 ]);
390
391 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
392
393 $content = json_decode($this->client->getResponse()->getContent(), true);
394
395 $this->assertGreaterThan(0, $content['id']);
396 $this->assertEquals('http://www.lemonde.fr/idees/article/2016/02/08/preserver-la-liberte-d-expression-sur-les-reseaux-sociaux_4861503_3232.html', $content['url']);
397 $this->assertEquals(false, $content['is_archived']);
398 $this->assertEquals(true, $content['is_starred']);
399 }
400
401 public function testPatchEntry()
402 {
403 $entry = $this->client->getContainer()
404 ->get('doctrine.orm.entity_manager')
405 ->getRepository('WallabagCoreBundle:Entry')
406 ->findOneByUser(1);
407
408 if (!$entry) {
409 $this->markTestSkipped('No content found in db.');
410 }
411
412 // hydrate the tags relations
413 $nbTags = count($entry->getTags());
414
415 $this->client->request('PATCH', '/api/entries/'.$entry->getId().'.json', [
416 'title' => 'New awesome title',
417 'tags' => 'new tag '.uniqid(),
418 'starred' => '1',
419 'archive' => '0',
420 ]);
421
422 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
423
424 $content = json_decode($this->client->getResponse()->getContent(), true);
425
426 $this->assertEquals($entry->getId(), $content['id']);
427 $this->assertEquals($entry->getUrl(), $content['url']);
428 $this->assertEquals('New awesome title', $content['title']);
429 $this->assertGreaterThan($nbTags, count($content['tags']));
430 $this->assertEquals(1, $content['user_id']);
431 }
432
433 public function testPatchEntryWithoutQuotes()
434 {
435 $entry = $this->client->getContainer()
436 ->get('doctrine.orm.entity_manager')
437 ->getRepository('WallabagCoreBundle:Entry')
438 ->findOneByUser(1);
439
440 if (!$entry) {
441 $this->markTestSkipped('No content found in db.');
442 }
443
444 // hydrate the tags relations
445 $nbTags = count($entry->getTags());
446
447 $this->client->request('PATCH', '/api/entries/'.$entry->getId().'.json', [
448 'title' => 'New awesome title',
449 'tags' => 'new tag '.uniqid(),
450 'starred' => 1,
451 'archive' => 0,
452 ]);
453
454 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
455
456 $content = json_decode($this->client->getResponse()->getContent(), true);
457
458 $this->assertEquals($entry->getId(), $content['id']);
459 $this->assertEquals($entry->getUrl(), $content['url']);
460 $this->assertEquals('New awesome title', $content['title']);
461 $this->assertGreaterThan($nbTags, count($content['tags']));
462 }
463
464 public function testGetTagsEntry()
465 {
466 $entry = $this->client->getContainer()
467 ->get('doctrine.orm.entity_manager')
468 ->getRepository('WallabagCoreBundle:Entry')
469 ->findOneWithTags($this->user->getId());
470
471 $entry = $entry[0];
472
473 if (!$entry) {
474 $this->markTestSkipped('No content found in db.');
475 }
476
477 $tags = [];
478 foreach ($entry->getTags() as $tag) {
479 $tags[] = ['id' => $tag->getId(), 'label' => $tag->getLabel(), 'slug' => $tag->getSlug()];
480 }
481
482 $this->client->request('GET', '/api/entries/'.$entry->getId().'/tags');
483
484 $this->assertEquals(json_encode($tags, JSON_HEX_QUOT), $this->client->getResponse()->getContent());
485 }
486
487 public function testPostTagsOnEntry()
488 {
489 $entry = $this->client->getContainer()
490 ->get('doctrine.orm.entity_manager')
491 ->getRepository('WallabagCoreBundle:Entry')
492 ->findOneByUser(1);
493
494 if (!$entry) {
495 $this->markTestSkipped('No content found in db.');
496 }
497
498 $nbTags = count($entry->getTags());
499
500 $newTags = 'tag1,tag2,tag3';
501
502 $this->client->request('POST', '/api/entries/'.$entry->getId().'/tags', ['tags' => $newTags]);
503
504 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
505
506 $content = json_decode($this->client->getResponse()->getContent(), true);
507
508 $this->assertArrayHasKey('tags', $content);
509 $this->assertEquals($nbTags + 3, count($content['tags']));
510
511 $entryDB = $this->client->getContainer()
512 ->get('doctrine.orm.entity_manager')
513 ->getRepository('WallabagCoreBundle:Entry')
514 ->find($entry->getId());
515
516 $tagsInDB = [];
517 foreach ($entryDB->getTags()->toArray() as $tag) {
518 $tagsInDB[$tag->getId()] = $tag->getLabel();
519 }
520
521 foreach (explode(',', $newTags) as $tag) {
522 $this->assertContains($tag, $tagsInDB);
523 }
524 }
525
526 public function testDeleteOneTagEntry()
527 {
528 $entry = $this->client->getContainer()
529 ->get('doctrine.orm.entity_manager')
530 ->getRepository('WallabagCoreBundle:Entry')
531 ->findOneWithTags($this->user->getId());
532 $entry = $entry[0];
533
534 if (!$entry) {
535 $this->markTestSkipped('No content found in db.');
536 }
537
538 // hydrate the tags relations
539 $nbTags = count($entry->getTags());
540 $tag = $entry->getTags()[0];
541
542 $this->client->request('DELETE', '/api/entries/'.$entry->getId().'/tags/'.$tag->getId().'.json');
543
544 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
545
546 $content = json_decode($this->client->getResponse()->getContent(), true);
547
548 $this->assertArrayHasKey('tags', $content);
549 $this->assertEquals($nbTags - 1, count($content['tags']));
550 }
551
552 public function testGetUserTags()
553 {
554 $this->client->request('GET', '/api/tags.json');
555
556 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
557
558 $content = json_decode($this->client->getResponse()->getContent(), true);
559
560 $this->assertGreaterThan(0, $content);
561 $this->assertArrayHasKey('id', $content[0]);
562 $this->assertArrayHasKey('label', $content[0]);
563
564 return end($content);
565 }
566
567 /**
568 * @depends testGetUserTags
569 */
570 public function testDeleteUserTag($tag)
571 {
572 $tagName = $tag['label'];
573
574 $this->client->request('DELETE', '/api/tags/'.$tag['id'].'.json');
575
576 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
577
578 $content = json_decode($this->client->getResponse()->getContent(), true);
579
580 $this->assertArrayHasKey('label', $content);
581 $this->assertEquals($tag['label'], $content['label']);
582 $this->assertEquals($tag['slug'], $content['slug']);
583
584 $entries = $this->client->getContainer()
585 ->get('doctrine.orm.entity_manager')
586 ->getRepository('WallabagCoreBundle:Entry')
587 ->findAllByTagId($this->user->getId(), $tag['id']);
588
589 $this->assertCount(0, $entries);
590
591 $tag = $this->client->getContainer()
592 ->get('doctrine.orm.entity_manager')
593 ->getRepository('WallabagCoreBundle:Tag')
594 ->findOneByLabel($tagName);
595
596 $this->assertNull($tag, $tagName.' was removed because it begun an orphan tag');
597 }
598
599 public function testDeleteTagByLabel()
600 {
601 $em = $this->client->getContainer()->get('doctrine.orm.entity_manager');
602 $entry = $this->client->getContainer()
603 ->get('doctrine.orm.entity_manager')
604 ->getRepository('WallabagCoreBundle:Entry')
605 ->findOneWithTags($this->user->getId());
606
607 $entry = $entry[0];
608
609 $tag = new Tag();
610 $tag->setLabel('Awesome tag for test');
611 $em->persist($tag);
612
613 $entry->addTag($tag);
614
615 $em->persist($entry);
616 $em->flush();
617
618 $this->client->request('DELETE', '/api/tag/label.json', ['tag' => $tag->getLabel()]);
619
620 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
621
622 $content = json_decode($this->client->getResponse()->getContent(), true);
623
624 $this->assertArrayHasKey('label', $content);
625 $this->assertEquals($tag->getLabel(), $content['label']);
626 $this->assertEquals($tag->getSlug(), $content['slug']);
627
628 $entries = $this->client->getContainer()
629 ->get('doctrine.orm.entity_manager')
630 ->getRepository('WallabagCoreBundle:Entry')
631 ->findAllByTagId($this->user->getId(), $tag->getId());
632
633 $this->assertCount(0, $entries);
634 }
635
636 public function testDeleteTagByLabelNotFound()
637 {
638 $this->client->request('DELETE', '/api/tag/label.json', ['tag' => 'does not exist']);
639
640 $this->assertEquals(404, $this->client->getResponse()->getStatusCode());
641 }
642
643 public function testDeleteTagsByLabel()
644 {
645 $em = $this->client->getContainer()->get('doctrine.orm.entity_manager');
646 $entry = $this->client->getContainer()
647 ->get('doctrine.orm.entity_manager')
648 ->getRepository('WallabagCoreBundle:Entry')
649 ->findOneWithTags($this->user->getId());
650
651 $entry = $entry[0];
652
653 $tag = new Tag();
654 $tag->setLabel('Awesome tag for tagsLabel');
655 $em->persist($tag);
656
657 $tag2 = new Tag();
658 $tag2->setLabel('Awesome tag for tagsLabel 2');
659 $em->persist($tag2);
660
661 $entry->addTag($tag);
662 $entry->addTag($tag2);
663
664 $em->persist($entry);
665 $em->flush();
666
667 $this->client->request('DELETE', '/api/tags/label.json', ['tags' => $tag->getLabel().','.$tag2->getLabel()]);
668
669 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
670
671 $content = json_decode($this->client->getResponse()->getContent(), true);
672
673 $this->assertCount(2, $content);
674
675 $this->assertArrayHasKey('label', $content[0]);
676 $this->assertEquals($tag->getLabel(), $content[0]['label']);
677 $this->assertEquals($tag->getSlug(), $content[0]['slug']);
678
679 $this->assertArrayHasKey('label', $content[1]);
680 $this->assertEquals($tag2->getLabel(), $content[1]['label']);
681 $this->assertEquals($tag2->getSlug(), $content[1]['slug']);
682
683 $entries = $this->client->getContainer()
684 ->get('doctrine.orm.entity_manager')
685 ->getRepository('WallabagCoreBundle:Entry')
686 ->findAllByTagId($this->user->getId(), $tag->getId());
687
688 $this->assertCount(0, $entries);
689
690 $entries = $this->client->getContainer()
691 ->get('doctrine.orm.entity_manager')
692 ->getRepository('WallabagCoreBundle:Entry')
693 ->findAllByTagId($this->user->getId(), $tag2->getId());
694
695 $this->assertCount(0, $entries);
696 }
697
698 public function testDeleteTagsByLabelNotFound()
699 {
700 $this->client->request('DELETE', '/api/tags/label.json', ['tags' => 'does not exist']);
701
702 $this->assertEquals(404, $this->client->getResponse()->getStatusCode());
703 }
704
705 public function testGetVersion() 9 public function testGetVersion()
706 { 10 {
707 $this->client->request('GET', '/api/version'); 11 $this->client->request('GET', '/api/version');
@@ -712,136 +16,4 @@ class WallabagRestControllerTest extends WallabagApiTestCase
712 16
713 $this->assertEquals($this->client->getContainer()->getParameter('wallabag_core.version'), $content); 17 $this->assertEquals($this->client->getContainer()->getParameter('wallabag_core.version'), $content);
714 } 18 }
715
716 public function testSaveIsArchivedAfterPost()
717 {
718 $entry = $this->client->getContainer()
719 ->get('doctrine.orm.entity_manager')
720 ->getRepository('WallabagCoreBundle:Entry')
721 ->findOneBy(['user' => 1, 'isArchived' => true]);
722
723 if (!$entry) {
724 $this->markTestSkipped('No content found in db.');
725 }
726
727 $this->client->request('POST', '/api/entries.json', [
728 'url' => $entry->getUrl(),
729 ]);
730
731 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
732
733 $content = json_decode($this->client->getResponse()->getContent(), true);
734
735 $this->assertEquals(true, $content['is_archived']);
736 }
737
738 public function testSaveIsStarredAfterPost()
739 {
740 $entry = $this->client->getContainer()
741 ->get('doctrine.orm.entity_manager')
742 ->getRepository('WallabagCoreBundle:Entry')
743 ->findOneBy(['user' => 1, 'isStarred' => true]);
744
745 if (!$entry) {
746 $this->markTestSkipped('No content found in db.');
747 }
748
749 $this->client->request('POST', '/api/entries.json', [
750 'url' => $entry->getUrl(),
751 ]);
752
753 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
754
755 $content = json_decode($this->client->getResponse()->getContent(), true);
756
757 $this->assertEquals(true, $content['is_starred']);
758 }
759
760 public function testSaveIsArchivedAfterPatch()
761 {
762 $entry = $this->client->getContainer()
763 ->get('doctrine.orm.entity_manager')
764 ->getRepository('WallabagCoreBundle:Entry')
765 ->findOneBy(['user' => 1, 'isArchived' => true]);
766
767 if (!$entry) {
768 $this->markTestSkipped('No content found in db.');
769 }
770
771 $this->client->request('PATCH', '/api/entries/'.$entry->getId().'.json', [
772 'title' => $entry->getTitle().'++',
773 ]);
774
775 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
776
777 $content = json_decode($this->client->getResponse()->getContent(), true);
778
779 $this->assertEquals(true, $content['is_archived']);
780 }
781
782 public function testSaveIsStarredAfterPatch()
783 {
784 $entry = $this->client->getContainer()
785 ->get('doctrine.orm.entity_manager')
786 ->getRepository('WallabagCoreBundle:Entry')
787 ->findOneBy(['user' => 1, 'isStarred' => true]);
788
789 if (!$entry) {
790 $this->markTestSkipped('No content found in db.');
791 }
792 $this->client->request('PATCH', '/api/entries/'.$entry->getId().'.json', [
793 'title' => $entry->getTitle().'++',
794 ]);
795
796 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
797
798 $content = json_decode($this->client->getResponse()->getContent(), true);
799
800 $this->assertEquals(true, $content['is_starred']);
801 }
802
803 public function testGetEntriesExists()
804 {
805 $this->client->request('GET', '/api/entries/exists?url=http://0.0.0.0/entry2');
806
807 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
808
809 $content = json_decode($this->client->getResponse()->getContent(), true);
810
811 $this->assertEquals(true, $content['exists']);
812 }
813
814 public function testGetEntriesExistsWithManyUrls()
815 {
816 $url1 = 'http://0.0.0.0/entry2';
817 $url2 = 'http://0.0.0.0/entry10';
818 $this->client->request('GET', '/api/entries/exists?urls[]='.$url1.'&urls[]='.$url2);
819
820 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
821
822 $content = json_decode($this->client->getResponse()->getContent(), true);
823
824 $this->assertArrayHasKey($url1, $content);
825 $this->assertArrayHasKey($url2, $content);
826 $this->assertEquals(true, $content[$url1]);
827 $this->assertEquals(false, $content[$url2]);
828 }
829
830 public function testGetEntriesExistsWhichDoesNotExists()
831 {
832 $this->client->request('GET', '/api/entries/exists?url=http://google.com/entry2');
833
834 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
835
836 $content = json_decode($this->client->getResponse()->getContent(), true);
837
838 $this->assertEquals(false, $content['exists']);
839 }
840
841 public function testGetEntriesExistsWithNoUrl()
842 {
843 $this->client->request('GET', '/api/entries/exists?url=');
844
845 $this->assertEquals(403, $this->client->getResponse()->getStatusCode());
846 }
847} 19}
diff --git a/tests/Wallabag/CoreBundle/Controller/ExportControllerTest.php b/tests/Wallabag/CoreBundle/Controller/ExportControllerTest.php
index 9ecd8bc4..5ca886bd 100644
--- a/tests/Wallabag/CoreBundle/Controller/ExportControllerTest.php
+++ b/tests/Wallabag/CoreBundle/Controller/ExportControllerTest.php
@@ -117,6 +117,17 @@ class ExportControllerTest extends WallabagCoreTestCase
117 $this->assertEquals('application/pdf', $headers->get('content-type')); 117 $this->assertEquals('application/pdf', $headers->get('content-type'));
118 $this->assertEquals('attachment; filename="All articles.pdf"', $headers->get('content-disposition')); 118 $this->assertEquals('attachment; filename="All articles.pdf"', $headers->get('content-disposition'));
119 $this->assertEquals('binary', $headers->get('content-transfer-encoding')); 119 $this->assertEquals('binary', $headers->get('content-transfer-encoding'));
120
121 ob_start();
122 $crawler = $client->request('GET', '/export/tag_entries.pdf?tag=foo');
123 ob_end_clean();
124
125 $this->assertEquals(200, $client->getResponse()->getStatusCode());
126
127 $headers = $client->getResponse()->headers;
128 $this->assertEquals('application/pdf', $headers->get('content-type'));
129 $this->assertEquals('attachment; filename="Tag_entries articles.pdf"', $headers->get('content-disposition'));
130 $this->assertEquals('binary', $headers->get('content-transfer-encoding'));
120 } 131 }
121 132
122 public function testTxtExport() 133 public function testTxtExport()
diff --git a/tests/Wallabag/UserBundle/Mailer/AuthCodeMailerTest.php b/tests/Wallabag/UserBundle/Mailer/AuthCodeMailerTest.php
index 441d6519..21412da6 100644
--- a/tests/Wallabag/UserBundle/Mailer/AuthCodeMailerTest.php
+++ b/tests/Wallabag/UserBundle/Mailer/AuthCodeMailerTest.php
@@ -26,7 +26,6 @@ class AuthCodeMailerTest extends \PHPUnit_Framework_TestCase
26 protected $mailer; 26 protected $mailer;
27 protected $spool; 27 protected $spool;
28 protected $twig; 28 protected $twig;
29 protected $config;
30 29
31 protected function setUp() 30 protected function setUp()
32 { 31 {
@@ -44,14 +43,6 @@ class AuthCodeMailerTest extends \PHPUnit_Framework_TestCase
44TWIG; 43TWIG;
45 44
46 $this->twig = new \Twig_Environment(new \Twig_Loader_Array(['WallabagUserBundle:TwoFactor:email_auth_code.html.twig' => $twigTemplate])); 45 $this->twig = new \Twig_Environment(new \Twig_Loader_Array(['WallabagUserBundle:TwoFactor:email_auth_code.html.twig' => $twigTemplate]));
47
48 $this->config = $this->getMockBuilder('Craue\ConfigBundle\Util\Config')
49 ->disableOriginalConstructor()
50 ->getMock();
51
52 $this->config->expects($this->any())
53 ->method('get')
54 ->willReturn('http://0.0.0.0/support');
55 } 46 }
56 47
57 public function testSendEmail() 48 public function testSendEmail()
@@ -67,7 +58,8 @@ TWIG;
67 $this->twig, 58 $this->twig,
68 'nobody@test.io', 59 'nobody@test.io',
69 'wallabag test', 60 'wallabag test',
70 $this->config 61 'http://0.0.0.0/support',
62 'http://0.0.0.0/'
71 ); 63 );
72 64
73 $authCodeMailer->sendAuthCode($user); 65 $authCodeMailer->sendAuthCode($user);