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/index.rst1
-rw-r--r--docs/de/user/backup.rst25
-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/index.rst1
-rw-r--r--docs/en/user/android.rst2
-rw-r--r--docs/en/user/backup.rst25
-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/index.rst1
-rw-r--r--docs/fr/user/backup.rst26
-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.php367
-rw-r--r--src/Wallabag/ApiBundle/Controller/TagRestController.php171
-rw-r--r--src/Wallabag/ApiBundle/Controller/WallabagRestController.php522
-rw-r--r--src/Wallabag/ApiBundle/Resources/config/routing_rest.yml7
-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/Repository/EntryRepository.php2
-rw-r--r--src/Wallabag/CoreBundle/Resources/config/services.yml3
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.da.yml2
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.de.yml2
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.en.yml2
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.es.yml2
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml2
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml726
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.it.yml2
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml2
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml2
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.pt.yml492
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml2
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml2
-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/ImportBundle/Resources/config/services.yml1
-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.php673
-rw-r--r--tests/Wallabag/ApiBundle/Controller/TagRestControllerTest.php162
-rw-r--r--tests/Wallabag/ApiBundle/Controller/WallabagRestControllerTest.php820
-rw-r--r--tests/Wallabag/CoreBundle/Controller/ExportControllerTest.php11
-rw-r--r--tests/Wallabag/UserBundle/Mailer/AuthCodeMailerTest.php12
69 files changed, 2535 insertions, 1801 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 a56cbdd9..81d1728a 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/index.rst b/docs/de/index.rst
index a380d7bd..b422bf83 100644
--- a/docs/de/index.rst
+++ b/docs/de/index.rst
@@ -45,6 +45,7 @@ Die Dokumentation ist in anderen Sprachen verfügbar :
45 user/tags 45 user/tags
46 user/android 46 user/android
47 user/parameters 47 user/parameters
48 user/backup
48 49
49.. _dev-docs: 50.. _dev-docs:
50 51
diff --git a/docs/de/user/backup.rst b/docs/de/user/backup.rst
new file mode 100644
index 00000000..ffe3ce37
--- /dev/null
+++ b/docs/de/user/backup.rst
@@ -0,0 +1,25 @@
1wallabag sichern
2================
3Da es manchmal vorkommen kann, dass dir ein Fehler mit deiner wallabag unterläuft und du Daten verlierst oder deine wallabag auf einen anderen Server verschieben willst, ist eine Sicherung der Daten sicher ratsam.
4Dieser Artikel beschreibt, was du für die Sicherung benötigst.
5
6Grundlegende Einstellungen
7--------------------------
8wallabag speichert grundlegende Parameter (etwa der SMTP-Server oder das Datenbank-Backend) in der Datei `app/config/parameters.yml`.
9
10Datenbank
11---------
12Da wallabag verschiedene Datenbank-Typen unterstützt, hängt der Weg der Sicherung von dem verwendeten Typ ab. Daher verweisen wir an dieser Stelle auf die entsprechenden Dokumentationen:
13
14Hier sind einige Beispiele:
15
16- MySQL: http://dev.mysql.com/doc/refman/5.7/en/backup-methods.html
17- PostgreSQL: https://www.postgresql.org/docs/current/static/backup.html
18
19SQLite
20~~~~~~
21Um die SQLite-Datenbank zu sichern, ist es lediglich notwendig, das Verzeichnis `data/db` aus dem wallabag-Installations-Ordner zu kopieren.
22
23Bilder
24------
25Die Bilder, die von wallabag empfangen worden, sind unter `data/assets/images` gespeichert (der Bilder-Speicher wird in wallabag 2.2 implementiert).
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/index.rst b/docs/en/index.rst
index 6d85db2b..0ead261b 100644
--- a/docs/en/index.rst
+++ b/docs/en/index.rst
@@ -46,6 +46,7 @@ The documentation is available in other languages:
46 user/tags 46 user/tags
47 user/android 47 user/android
48 user/parameters 48 user/parameters
49 user/backup
49 50
50.. _dev-docs: 51.. _dev-docs:
51 52
diff --git a/docs/en/user/android.rst b/docs/en/user/android.rst
index ccbad264..91dcb2fc 100644
--- a/docs/en/user/android.rst
+++ b/docs/en/user/android.rst
@@ -79,7 +79,7 @@ Known limitations
792FA 792FA
80~~~ 80~~~
81 81
82Currently the does not support two-factor authentication. You should disable that to get the app working. 82Currently the Android application does not support two-factor authentication. You should disable that to get the application working.
83 83
84 84
85Limited amount of articles with wallabag v2 85Limited amount of articles with wallabag v2
diff --git a/docs/en/user/backup.rst b/docs/en/user/backup.rst
new file mode 100644
index 00000000..51721000
--- /dev/null
+++ b/docs/en/user/backup.rst
@@ -0,0 +1,25 @@
1Backup wallabag
2===============
3Because sometimes you may do a mistake with your wallabag and lose data or in case you need to move your wallabag to another server you want to backup your data.
4This articles describes what you need to backup.
5
6Basic settings
7--------------
8wallabag stores some basic parameters (like SMTP server or database backend) in the file `app/config/parameters.yml`.
9
10Database
11--------
12As wallabag supports different kinds of database, the way to perform the backup depends on the database you use, so you need to refer to the vendor documentation.
13
14Here's some examples:
15
16- MySQL: http://dev.mysql.com/doc/refman/5.7/en/backup-methods.html
17- PostgreSQL: https://www.postgresql.org/docs/current/static/backup.html
18
19SQLite
20~~~~~~
21To backup the SQLite database, you just need to copy the directory `data/db` from the wallabag application directory.
22
23Images
24------
25The images retrieved by wallabag are stored under `data/assets/images` (the images storage will be implemented in wallabag 2.2).
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/index.rst b/docs/fr/index.rst
index 2a265ff3..dbb85910 100644
--- a/docs/fr/index.rst
+++ b/docs/fr/index.rst
@@ -46,6 +46,7 @@ La documentation est disponible dans d'autres langues :
46 user/filters 46 user/filters
47 user/tags 47 user/tags
48 user/parameters 48 user/parameters
49 user/backup
49 50
50.. _dev-docs: 51.. _dev-docs:
51 52
diff --git a/docs/fr/user/backup.rst b/docs/fr/user/backup.rst
new file mode 100644
index 00000000..ec4b1f4d
--- /dev/null
+++ b/docs/fr/user/backup.rst
@@ -0,0 +1,26 @@
1Sauvegarde de wallabag
2======================
3
4Parce que des fois vous faites des erreurs avec votre installation de wallabag et vous perdez des données ou parce que vous souhaitez migrer votre installation sur un autre serveur, vous souhaitez faire une sauvegarde de vos données.
5Cette documentation décrit ce que vous devez sauvegarder.
6
7Configuration
8-------------
9wallabag stocke quelques paramètres (comme la configuration SMTP ou les infos de bases de données) dans le fichier `app/config/parameters.yml`.
10
11Base de données
12---------------
13Comme wallabag supporte différentes sortes de bases de données, la manière de sauvegarder dépend du type de base de données que vous utilisez, donc vous devez vous en référez à la documentation correspondante.
14
15Quelques exemples :
16
17- MySQL: http://dev.mysql.com/doc/refman/5.7/en/backup-methods.html
18- PostgreSQL: https://www.postgresql.org/docs/current/static/backup.html
19
20SQLite
21~~~~~~
22Pour sauvegarder une base SQLite, vous devez juste copier le répertoire `data/db` de votre installation wallabag.
23
24Images
25------
26Les images sauvegardées par wallabag sont stockées dans `data/assets/images` (le stockage des images sera implémenté dans wallabag 2.2).
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..24fa7b3b
--- /dev/null
+++ b/src/Wallabag/ApiBundle/Controller/EntryRestController.php
@@ -0,0 +1,367 @@
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;
13
14class EntryRestController extends WallabagRestController
15{
16 /**
17 * Check if an entry exist by url.
18 *
19 * @ApiDoc(
20 * parameters={
21 * {"name"="url", "dataType"="string", "required"=true, "format"="An url", "description"="Url to check if it exists"},
22 * {"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"}
23 * }
24 * )
25 *
26 * @return JsonResponse
27 */
28 public function getEntriesExistsAction(Request $request)
29 {
30 $this->validateAuthentication();
31
32 $urls = $request->query->get('urls', []);
33
34 // handle multiple urls first
35 if (!empty($urls)) {
36 $results = [];
37 foreach ($urls as $url) {
38 $res = $this->getDoctrine()
39 ->getRepository('WallabagCoreBundle:Entry')
40 ->findByUrlAndUserId($url, $this->getUser()->getId());
41
42 $results[$url] = false === $res ? false : true;
43 }
44
45 $json = $this->get('serializer')->serialize($results, 'json');
46
47 return (new JsonResponse())->setJson($json);
48 }
49
50 // let's see if it is a simple url?
51 $url = $request->query->get('url', '');
52
53 if (empty($url)) {
54 throw $this->createAccessDeniedException('URL is empty?, logged user id: '.$this->getUser()->getId());
55 }
56
57 $res = $this->getDoctrine()
58 ->getRepository('WallabagCoreBundle:Entry')
59 ->findByUrlAndUserId($url, $this->getUser()->getId());
60
61 $exists = false === $res ? false : true;
62
63 $json = $this->get('serializer')->serialize(['exists' => $exists], 'json');
64
65 return (new JsonResponse())->setJson($json);
66 }
67
68 /**
69 * Retrieve all entries. It could be filtered by many options.
70 *
71 * @ApiDoc(
72 * parameters={
73 * {"name"="archive", "dataType"="integer", "required"=false, "format"="1 or 0, all entries by default", "description"="filter by archived status."},
74 * {"name"="starred", "dataType"="integer", "required"=false, "format"="1 or 0, all entries by default", "description"="filter by starred status."},
75 * {"name"="sort", "dataType"="string", "required"=false, "format"="'created' or 'updated', default 'created'", "description"="sort entries by date."},
76 * {"name"="order", "dataType"="string", "required"=false, "format"="'asc' or 'desc', default 'desc'", "description"="order of sort."},
77 * {"name"="page", "dataType"="integer", "required"=false, "format"="default '1'", "description"="what page you want."},
78 * {"name"="perPage", "dataType"="integer", "required"=false, "format"="default'30'", "description"="results per page."},
79 * {"name"="tags", "dataType"="string", "required"=false, "format"="api,rest", "description"="a list of tags url encoded. Will returns entries that matches ALL tags."},
80 * {"name"="since", "dataType"="integer", "required"=false, "format"="default '0'", "description"="The timestamp since when you want entries updated."},
81 * }
82 * )
83 *
84 * @return JsonResponse
85 */
86 public function getEntriesAction(Request $request)
87 {
88 $this->validateAuthentication();
89
90 $isArchived = (null === $request->query->get('archive')) ? null : (bool) $request->query->get('archive');
91 $isStarred = (null === $request->query->get('starred')) ? null : (bool) $request->query->get('starred');
92 $sort = $request->query->get('sort', 'created');
93 $order = $request->query->get('order', 'desc');
94 $page = (int) $request->query->get('page', 1);
95 $perPage = (int) $request->query->get('perPage', 30);
96 $tags = $request->query->get('tags', '');
97 $since = $request->query->get('since', 0);
98
99 $pager = $this->getDoctrine()
100 ->getRepository('WallabagCoreBundle:Entry')
101 ->findEntries($this->getUser()->getId(), $isArchived, $isStarred, $sort, $order, $since, $tags);
102
103 $pager->setCurrentPage($page);
104 $pager->setMaxPerPage($perPage);
105
106 $pagerfantaFactory = new PagerfantaFactory('page', 'perPage');
107 $paginatedCollection = $pagerfantaFactory->createRepresentation(
108 $pager,
109 new Route(
110 'api_get_entries',
111 [
112 'archive' => $isArchived,
113 'starred' => $isStarred,
114 'sort' => $sort,
115 'order' => $order,
116 'page' => $page,
117 'perPage' => $perPage,
118 'tags' => $tags,
119 'since' => $since,
120 ],
121 UrlGeneratorInterface::ABSOLUTE_URL
122 )
123 );
124
125 $json = $this->get('serializer')->serialize($paginatedCollection, 'json');
126
127 return (new JsonResponse())->setJson($json);
128 }
129
130 /**
131 * Retrieve a single entry.
132 *
133 * @ApiDoc(
134 * requirements={
135 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
136 * }
137 * )
138 *
139 * @return JsonResponse
140 */
141 public function getEntryAction(Entry $entry)
142 {
143 $this->validateAuthentication();
144 $this->validateUserAccess($entry->getUser()->getId());
145
146 $json = $this->get('serializer')->serialize($entry, 'json');
147
148 return (new JsonResponse())->setJson($json);
149 }
150
151 /**
152 * Create an entry.
153 *
154 * @ApiDoc(
155 * parameters={
156 * {"name"="url", "dataType"="string", "required"=true, "format"="http://www.test.com/article.html", "description"="Url for the entry."},
157 * {"name"="title", "dataType"="string", "required"=false, "description"="Optional, we'll get the title from the page."},
158 * {"name"="tags", "dataType"="string", "required"=false, "format"="tag1,tag2,tag3", "description"="a comma-separated list of tags."},
159 * {"name"="starred", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="entry already starred"},
160 * {"name"="archive", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="entry already archived"},
161 * }
162 * )
163 *
164 * @return JsonResponse
165 */
166 public function postEntriesAction(Request $request)
167 {
168 $this->validateAuthentication();
169
170 $url = $request->request->get('url');
171 $title = $request->request->get('title');
172 $isArchived = $request->request->get('archive');
173 $isStarred = $request->request->get('starred');
174
175 $entry = $this->get('wallabag_core.entry_repository')->findByUrlAndUserId($url, $this->getUser()->getId());
176
177 if (false === $entry) {
178 $entry = $this->get('wallabag_core.content_proxy')->updateEntry(
179 new Entry($this->getUser()),
180 $url
181 );
182 }
183
184 if (!is_null($title)) {
185 $entry->setTitle($title);
186 }
187
188 $tags = $request->request->get('tags', '');
189 if (!empty($tags)) {
190 $this->get('wallabag_core.content_proxy')->assignTagsToEntry($entry, $tags);
191 }
192
193 if (!is_null($isStarred)) {
194 $entry->setStarred((bool) $isStarred);
195 }
196
197 if (!is_null($isArchived)) {
198 $entry->setArchived((bool) $isArchived);
199 }
200
201 $em = $this->getDoctrine()->getManager();
202 $em->persist($entry);
203
204 $em->flush();
205
206 $json = $this->get('serializer')->serialize($entry, 'json');
207
208 return (new JsonResponse())->setJson($json);
209 }
210
211 /**
212 * Change several properties of an entry.
213 *
214 * @ApiDoc(
215 * requirements={
216 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
217 * },
218 * parameters={
219 * {"name"="title", "dataType"="string", "required"=false},
220 * {"name"="tags", "dataType"="string", "required"=false, "format"="tag1,tag2,tag3", "description"="a comma-separated list of tags."},
221 * {"name"="archive", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="archived the entry."},
222 * {"name"="starred", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="starred the entry."},
223 * }
224 * )
225 *
226 * @return JsonResponse
227 */
228 public function patchEntriesAction(Entry $entry, Request $request)
229 {
230 $this->validateAuthentication();
231 $this->validateUserAccess($entry->getUser()->getId());
232
233 $title = $request->request->get('title');
234 $isArchived = $request->request->get('archive');
235 $isStarred = $request->request->get('starred');
236
237 if (!is_null($title)) {
238 $entry->setTitle($title);
239 }
240
241 if (!is_null($isArchived)) {
242 $entry->setArchived((bool) $isArchived);
243 }
244
245 if (!is_null($isStarred)) {
246 $entry->setStarred((bool) $isStarred);
247 }
248
249 $tags = $request->request->get('tags', '');
250 if (!empty($tags)) {
251 $this->get('wallabag_core.content_proxy')->assignTagsToEntry($entry, $tags);
252 }
253
254 $em = $this->getDoctrine()->getManager();
255 $em->flush();
256
257 $json = $this->get('serializer')->serialize($entry, 'json');
258
259 return (new JsonResponse())->setJson($json);
260 }
261
262 /**
263 * Delete **permanently** an entry.
264 *
265 * @ApiDoc(
266 * requirements={
267 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
268 * }
269 * )
270 *
271 * @return JsonResponse
272 */
273 public function deleteEntriesAction(Entry $entry)
274 {
275 $this->validateAuthentication();
276 $this->validateUserAccess($entry->getUser()->getId());
277
278 $em = $this->getDoctrine()->getManager();
279 $em->remove($entry);
280 $em->flush();
281
282 $json = $this->get('serializer')->serialize($entry, 'json');
283
284 return (new JsonResponse())->setJson($json);
285 }
286
287 /**
288 * Retrieve all tags for an entry.
289 *
290 * @ApiDoc(
291 * requirements={
292 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
293 * }
294 * )
295 *
296 * @return JsonResponse
297 */
298 public function getEntriesTagsAction(Entry $entry)
299 {
300 $this->validateAuthentication();
301 $this->validateUserAccess($entry->getUser()->getId());
302
303 $json = $this->get('serializer')->serialize($entry->getTags(), 'json');
304
305 return (new JsonResponse())->setJson($json);
306 }
307
308 /**
309 * Add one or more tags to an entry.
310 *
311 * @ApiDoc(
312 * requirements={
313 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
314 * },
315 * parameters={
316 * {"name"="tags", "dataType"="string", "required"=false, "format"="tag1,tag2,tag3", "description"="a comma-separated list of tags."},
317 * }
318 * )
319 *
320 * @return JsonResponse
321 */
322 public function postEntriesTagsAction(Request $request, Entry $entry)
323 {
324 $this->validateAuthentication();
325 $this->validateUserAccess($entry->getUser()->getId());
326
327 $tags = $request->request->get('tags', '');
328 if (!empty($tags)) {
329 $this->get('wallabag_core.content_proxy')->assignTagsToEntry($entry, $tags);
330 }
331
332 $em = $this->getDoctrine()->getManager();
333 $em->persist($entry);
334 $em->flush();
335
336 $json = $this->get('serializer')->serialize($entry, 'json');
337
338 return (new JsonResponse())->setJson($json);
339 }
340
341 /**
342 * Permanently remove one tag for an entry.
343 *
344 * @ApiDoc(
345 * requirements={
346 * {"name"="tag", "dataType"="integer", "requirement"="\w+", "description"="The tag ID"},
347 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
348 * }
349 * )
350 *
351 * @return JsonResponse
352 */
353 public function deleteEntriesTagsAction(Entry $entry, Tag $tag)
354 {
355 $this->validateAuthentication();
356 $this->validateUserAccess($entry->getUser()->getId());
357
358 $entry->removeTag($tag);
359 $em = $this->getDoctrine()->getManager();
360 $em->persist($entry);
361 $em->flush();
362
363 $json = $this->get('serializer')->serialize($entry, 'json');
364
365 return (new JsonResponse())->setJson($json);
366 }
367}
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 9997913d..e927a890 100644
--- a/src/Wallabag/ApiBundle/Controller/WallabagRestController.php
+++ b/src/Wallabag/ApiBundle/Controller/WallabagRestController.php
@@ -3,19 +3,12 @@
3namespace Wallabag\ApiBundle\Controller; 3namespace Wallabag\ApiBundle\Controller;
4 4
5use FOS\RestBundle\Controller\FOSRestController; 5use FOS\RestBundle\Controller\FOSRestController;
6use Hateoas\Configuration\Route;
7use Hateoas\Representation\Factory\PagerfantaFactory;
8use Nelmio\ApiDocBundle\Annotation\ApiDoc;
9use Symfony\Component\HttpFoundation\Request;
10use Symfony\Component\HttpFoundation\JsonResponse;
11use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
12use Symfony\Component\Security\Core\Exception\AccessDeniedException; 6use Symfony\Component\Security\Core\Exception\AccessDeniedException;
13use Wallabag\CoreBundle\Entity\Entry; 7use Wallabag\CoreBundle\Entity\Entry;
14use Wallabag\CoreBundle\Entity\Tag;
15 8
16class WallabagRestController extends FOSRestController 9class WallabagRestController extends FOSRestController
17{ 10{
18 private function validateAuthentication() 11 protected function validateAuthentication()
19 { 12 {
20 if (false === $this->get('security.authorization_checker')->isGranted('IS_AUTHENTICATED_FULLY')) { 13 if (false === $this->get('security.authorization_checker')->isGranted('IS_AUTHENTICATED_FULLY')) {
21 throw new AccessDeniedException(); 14 throw new AccessDeniedException();
@@ -23,523 +16,12 @@ class WallabagRestController extends FOSRestController
23 } 16 }
24 17
25 /** 18 /**
26 * Check if an entry exist by url.
27 *
28 * @ApiDoc(
29 * parameters={
30 * {"name"="url", "dataType"="string", "required"=true, "format"="An url", "description"="Url to check if it exists"},
31 * {"name"="urls", "dataType"="string", "required"=false, "format"="An array of urls (?urls[]=http...&urls[]=http...)", "description"="Urls (as an array) to check if it exists"}
32 * }
33 * )
34 *
35 * @return JsonResponse
36 */
37 public function getEntriesExistsAction(Request $request)
38 {
39 $this->validateAuthentication();
40
41 $urls = $request->query->get('urls', []);
42
43 // handle multiple urls first
44 if (!empty($urls)) {
45 $results = [];
46 foreach ($urls as $url) {
47 $res = $this->getDoctrine()
48 ->getRepository('WallabagCoreBundle:Entry')
49 ->findByUrlAndUserId($url, $this->getUser()->getId());
50
51 $results[$url] = false === $res ? false : true;
52 }
53
54 $json = $this->get('serializer')->serialize($results, 'json');
55
56 return (new JsonResponse())->setJson($json);
57 }
58
59 // let's see if it is a simple url?
60 $url = $request->query->get('url', '');
61
62 if (empty($url)) {
63 throw $this->createAccessDeniedException('URL is empty?, logged user id: '.$this->getUser()->getId());
64 }
65
66 $res = $this->getDoctrine()
67 ->getRepository('WallabagCoreBundle:Entry')
68 ->findByUrlAndUserId($url, $this->getUser()->getId());
69
70 $exists = false === $res ? false : true;
71
72 $json = $this->get('serializer')->serialize(['exists' => $exists], 'json');
73
74 return (new JsonResponse())->setJson($json);
75 }
76
77 /**
78 * Retrieve all entries. It could be filtered by many options.
79 *
80 * @ApiDoc(
81 * parameters={
82 * {"name"="archive", "dataType"="integer", "required"=false, "format"="1 or 0, all entries by default", "description"="filter by archived status."},
83 * {"name"="starred", "dataType"="integer", "required"=false, "format"="1 or 0, all entries by default", "description"="filter by starred status."},
84 * {"name"="sort", "dataType"="string", "required"=false, "format"="'created' or 'updated', default 'created'", "description"="sort entries by date."},
85 * {"name"="order", "dataType"="string", "required"=false, "format"="'asc' or 'desc', default 'desc'", "description"="order of sort."},
86 * {"name"="page", "dataType"="integer", "required"=false, "format"="default '1'", "description"="what page you want."},
87 * {"name"="perPage", "dataType"="integer", "required"=false, "format"="default'30'", "description"="results per page."},
88 * {"name"="tags", "dataType"="string", "required"=false, "format"="api,rest", "description"="a list of tags url encoded. Will returns entries that matches ALL tags."},
89 * {"name"="since", "dataType"="integer", "required"=false, "format"="default '0'", "description"="The timestamp since when you want entries updated."},
90 * }
91 * )
92 *
93 * @return JsonResponse
94 */
95 public function getEntriesAction(Request $request)
96 {
97 $this->validateAuthentication();
98
99 $isArchived = (null === $request->query->get('archive')) ? null : (bool) $request->query->get('archive');
100 $isStarred = (null === $request->query->get('starred')) ? null : (bool) $request->query->get('starred');
101 $sort = $request->query->get('sort', 'created');
102 $order = $request->query->get('order', 'desc');
103 $page = (int) $request->query->get('page', 1);
104 $perPage = (int) $request->query->get('perPage', 30);
105 $tags = $request->query->get('tags', '');
106 $since = $request->query->get('since', 0);
107
108 $pager = $this->getDoctrine()
109 ->getRepository('WallabagCoreBundle:Entry')
110 ->findEntries($this->getUser()->getId(), $isArchived, $isStarred, $sort, $order, $since, $tags);
111
112 $pager->setCurrentPage($page);
113 $pager->setMaxPerPage($perPage);
114
115 $pagerfantaFactory = new PagerfantaFactory('page', 'perPage');
116 $paginatedCollection = $pagerfantaFactory->createRepresentation(
117 $pager,
118 new Route(
119 'api_get_entries',
120 [
121 'archive' => $isArchived,
122 'starred' => $isStarred,
123 'sort' => $sort,
124 'order' => $order,
125 'page' => $page,
126 'perPage' => $perPage,
127 'tags' => $tags,
128 'since' => $since,
129 ],
130 UrlGeneratorInterface::ABSOLUTE_URL
131 )
132 );
133
134 $json = $this->get('serializer')->serialize($paginatedCollection, 'json');
135
136 return (new JsonResponse())->setJson($json);
137 }
138
139 /**
140 * Retrieve a single entry.
141 *
142 * @ApiDoc(
143 * requirements={
144 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
145 * }
146 * )
147 *
148 * @return JsonResponse
149 */
150 public function getEntryAction(Entry $entry)
151 {
152 $this->validateAuthentication();
153 $this->validateUserAccess($entry->getUser()->getId());
154
155 $json = $this->get('serializer')->serialize($entry, 'json');
156
157 return (new JsonResponse())->setJson($json);
158 }
159
160 /**
161 * Create an entry.
162 *
163 * @ApiDoc(
164 * parameters={
165 * {"name"="url", "dataType"="string", "required"=true, "format"="http://www.test.com/article.html", "description"="Url for the entry."},
166 * {"name"="title", "dataType"="string", "required"=false, "description"="Optional, we'll get the title from the page."},
167 * {"name"="tags", "dataType"="string", "required"=false, "format"="tag1,tag2,tag3", "description"="a comma-separated list of tags."},
168 * {"name"="starred", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="entry already starred"},
169 * {"name"="archive", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="entry already archived"},
170 * }
171 * )
172 *
173 * @return JsonResponse
174 */
175 public function postEntriesAction(Request $request)
176 {
177 $this->validateAuthentication();
178
179 $url = $request->request->get('url');
180 $title = $request->request->get('title');
181 $isArchived = $request->request->get('archive');
182 $isStarred = $request->request->get('starred');
183
184 $entry = $this->get('wallabag_core.entry_repository')->findByUrlAndUserId($url, $this->getUser()->getId());
185
186 if (false === $entry) {
187 $entry = $this->get('wallabag_core.content_proxy')->updateEntry(
188 new Entry($this->getUser()),
189 $url
190 );
191 }
192
193 if (!is_null($title)) {
194 $entry->setTitle($title);
195 }
196
197 $tags = $request->request->get('tags', '');
198 if (!empty($tags)) {
199 $this->get('wallabag_core.content_proxy')->assignTagsToEntry($entry, $tags);
200 }
201
202 if (!is_null($isStarred)) {
203 $entry->setStarred((bool) $isStarred);
204 }
205
206 if (!is_null($isArchived)) {
207 $entry->setArchived((bool) $isArchived);
208 }
209
210 $em = $this->getDoctrine()->getManager();
211 $em->persist($entry);
212
213 $em->flush();
214
215 $json = $this->get('serializer')->serialize($entry, 'json');
216
217 return (new JsonResponse())->setJson($json);
218 }
219
220 /**
221 * Change several properties of an entry.
222 *
223 * @ApiDoc(
224 * requirements={
225 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
226 * },
227 * parameters={
228 * {"name"="title", "dataType"="string", "required"=false},
229 * {"name"="tags", "dataType"="string", "required"=false, "format"="tag1,tag2,tag3", "description"="a comma-separated list of tags."},
230 * {"name"="archive", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="archived the entry."},
231 * {"name"="starred", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="starred the entry."},
232 * }
233 * )
234 *
235 * @return JsonResponse
236 */
237 public function patchEntriesAction(Entry $entry, Request $request)
238 {
239 $this->validateAuthentication();
240 $this->validateUserAccess($entry->getUser()->getId());
241
242 $title = $request->request->get('title');
243 $isArchived = $request->request->get('archive');
244 $isStarred = $request->request->get('starred');
245
246 if (!is_null($title)) {
247 $entry->setTitle($title);
248 }
249
250 if (!is_null($isArchived)) {
251 $entry->setArchived((bool) $isArchived);
252 }
253
254 if (!is_null($isStarred)) {
255 $entry->setStarred((bool) $isStarred);
256 }
257
258 $tags = $request->request->get('tags', '');
259 if (!empty($tags)) {
260 $this->get('wallabag_core.content_proxy')->assignTagsToEntry($entry, $tags);
261 }
262
263 $em = $this->getDoctrine()->getManager();
264 $em->flush();
265
266 $json = $this->get('serializer')->serialize($entry, 'json');
267
268 return (new JsonResponse())->setJson($json);
269 }
270
271 /**
272 * Delete **permanently** an entry.
273 *
274 * @ApiDoc(
275 * requirements={
276 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
277 * }
278 * )
279 *
280 * @return JsonResponse
281 */
282 public function deleteEntriesAction(Entry $entry)
283 {
284 $this->validateAuthentication();
285 $this->validateUserAccess($entry->getUser()->getId());
286
287 $em = $this->getDoctrine()->getManager();
288 $em->remove($entry);
289 $em->flush();
290
291 $json = $this->get('serializer')->serialize($entry, 'json');
292
293 return (new JsonResponse())->setJson($json);
294 }
295
296 /**
297 * Retrieve all tags for an entry.
298 *
299 * @ApiDoc(
300 * requirements={
301 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
302 * }
303 * )
304 *
305 * @return JsonResponse
306 */
307 public function getEntriesTagsAction(Entry $entry)
308 {
309 $this->validateAuthentication();
310 $this->validateUserAccess($entry->getUser()->getId());
311
312 $json = $this->get('serializer')->serialize($entry->getTags(), 'json');
313
314 return (new JsonResponse())->setJson($json);
315 }
316
317 /**
318 * Add one or more tags to an entry.
319 *
320 * @ApiDoc(
321 * requirements={
322 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
323 * },
324 * parameters={
325 * {"name"="tags", "dataType"="string", "required"=false, "format"="tag1,tag2,tag3", "description"="a comma-separated list of tags."},
326 * }
327 * )
328 *
329 * @return JsonResponse
330 */
331 public function postEntriesTagsAction(Request $request, Entry $entry)
332 {
333 $this->validateAuthentication();
334 $this->validateUserAccess($entry->getUser()->getId());
335
336 $tags = $request->request->get('tags', '');
337 if (!empty($tags)) {
338 $this->get('wallabag_core.content_proxy')->assignTagsToEntry($entry, $tags);
339 }
340
341 $em = $this->getDoctrine()->getManager();
342 $em->persist($entry);
343 $em->flush();
344
345 $json = $this->get('serializer')->serialize($entry, 'json');
346
347 return (new JsonResponse())->setJson($json);
348 }
349
350 /**
351 * Permanently remove one tag for an entry.
352 *
353 * @ApiDoc(
354 * requirements={
355 * {"name"="tag", "dataType"="integer", "requirement"="\w+", "description"="The tag ID"},
356 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
357 * }
358 * )
359 *
360 * @return JsonResponse
361 */
362 public function deleteEntriesTagsAction(Entry $entry, Tag $tag)
363 {
364 $this->validateAuthentication();
365 $this->validateUserAccess($entry->getUser()->getId());
366
367 $entry->removeTag($tag);
368 $em = $this->getDoctrine()->getManager();
369 $em->persist($entry);
370 $em->flush();
371
372 $json = $this->get('serializer')->serialize($entry, 'json');
373
374 return (new JsonResponse())->setJson($json);
375 }
376
377 /**
378 * Retrieve all tags.
379 *
380 * @ApiDoc()
381 *
382 * @return JsonResponse
383 */
384 public function getTagsAction()
385 {
386 $this->validateAuthentication();
387
388 $tags = $this->getDoctrine()
389 ->getRepository('WallabagCoreBundle:Tag')
390 ->findAllTags($this->getUser()->getId());
391
392 $json = $this->get('serializer')->serialize($tags, 'json');
393
394 return (new JsonResponse())->setJson($json);
395 }
396
397 /**
398 * Permanently remove one tag from **every** entry.
399 *
400 * @ApiDoc(
401 * requirements={
402 * {"name"="tag", "dataType"="string", "required"=true, "requirement"="\w+", "description"="Tag as a string"}
403 * }
404 * )
405 *
406 * @return JsonResponse
407 */
408 public function deleteTagLabelAction(Request $request)
409 {
410 $this->validateAuthentication();
411 $label = $request->request->get('tag', '');
412
413 $tag = $this->getDoctrine()->getRepository('WallabagCoreBundle:Tag')->findOneByLabel($label);
414
415 if (empty($tag)) {
416 throw $this->createNotFoundException('Tag not found');
417 }
418
419 $this->getDoctrine()
420 ->getRepository('WallabagCoreBundle:Entry')
421 ->removeTag($this->getUser()->getId(), $tag);
422
423 $this->cleanOrphanTag($tag);
424
425 $json = $this->get('serializer')->serialize($tag, 'json');
426
427 return (new JsonResponse())->setJson($json);
428 }
429
430 /**
431 * Permanently remove some tags from **every** entry.
432 *
433 * @ApiDoc(
434 * requirements={
435 * {"name"="tags", "dataType"="string", "required"=true, "format"="tag1,tag2", "description"="Tags as strings (comma splitted)"}
436 * }
437 * )
438 *
439 * @return JsonResponse
440 */
441 public function deleteTagsLabelAction(Request $request)
442 {
443 $this->validateAuthentication();
444
445 $tagsLabels = $request->request->get('tags', '');
446
447 $tags = [];
448
449 foreach (explode(',', $tagsLabels) as $tagLabel) {
450 $tagEntity = $this->getDoctrine()->getRepository('WallabagCoreBundle:Tag')->findOneByLabel($tagLabel);
451
452 if (!empty($tagEntity)) {
453 $tags[] = $tagEntity;
454 }
455 }
456
457 if (empty($tags)) {
458 throw $this->createNotFoundException('Tags not found');
459 }
460
461 $this->getDoctrine()
462 ->getRepository('WallabagCoreBundle:Entry')
463 ->removeTags($this->getUser()->getId(), $tags);
464
465 $this->cleanOrphanTag($tags);
466
467 $json = $this->get('serializer')->serialize($tags, 'json');
468
469 return (new JsonResponse())->setJson($json);
470 }
471
472 /**
473 * Permanently remove one tag from **every** entry.
474 *
475 * @ApiDoc(
476 * requirements={
477 * {"name"="tag", "dataType"="integer", "requirement"="\w+", "description"="The tag"}
478 * }
479 * )
480 *
481 * @return JsonResponse
482 */
483 public function deleteTagAction(Tag $tag)
484 {
485 $this->validateAuthentication();
486
487 $this->getDoctrine()
488 ->getRepository('WallabagCoreBundle:Entry')
489 ->removeTag($this->getUser()->getId(), $tag);
490
491 $this->cleanOrphanTag($tag);
492
493 $json = $this->get('serializer')->serialize($tag, 'json');
494
495 return (new JsonResponse())->setJson($json);
496 }
497
498 /**
499 * Retrieve version number.
500 *
501 * @ApiDoc()
502 *
503 * @return JsonResponse
504 */
505 public function getVersionAction()
506 {
507 $version = $this->container->getParameter('wallabag_core.version');
508
509 $json = $this->get('serializer')->serialize($version, 'json');
510
511 return (new JsonResponse())->setJson($json);
512 }
513
514 /**
515 * Remove orphan tag in case no entries are associated to it.
516 *
517 * @param Tag|array $tags
518 */
519 private function cleanOrphanTag($tags)
520 {
521 if (!is_array($tags)) {
522 $tags = [$tags];
523 }
524
525 $em = $this->getDoctrine()->getManager();
526
527 foreach ($tags as $tag) {
528 if (count($tag->getEntries()) === 0) {
529 $em->remove($tag);
530 }
531 }
532
533 $em->flush();
534 }
535
536 /**
537 * Validate that the first id is equal to the second one. 19 * Validate that the first id is equal to the second one.
538 * If not, throw exception. It means a user try to access information from an other user. 20 * If not, throw exception. It means a user try to access information from an other user.
539 * 21 *
540 * @param int $requestUserId User id from the requested source 22 * @param int $requestUserId User id from the requested source
541 */ 23 */
542 private function validateUserAccess($requestUserId) 24 protected function validateUserAccess($requestUserId)
543 { 25 {
544 $user = $this->get('security.token_storage')->getToken()->getUser(); 26 $user = $this->get('security.token_storage')->getToken()->getUser();
545 if ($requestUserId != $user->getId()) { 27 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 5f43f971..c1af9e02 100644
--- a/src/Wallabag/ApiBundle/Resources/config/routing_rest.yml
+++ b/src/Wallabag/ApiBundle/Resources/config/routing_rest.yml
@@ -1,4 +1,9 @@
1entries: 1entries:
2 type: rest 2 type: rest
3 resource: "WallabagApiBundle:WallabagRest" 3 resource: "WallabagApiBundle:EntryRest"
4 name_prefix: api_
5
6tags:
7 type: rest
8 resource: "WallabagApiBundle:TagRest"
4 name_prefix: api_ 9 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 5acc6852..707f3bbe 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/Repository/EntryRepository.php b/src/Wallabag/CoreBundle/Repository/EntryRepository.php
index cd2b47b9..4f03ae0f 100644
--- a/src/Wallabag/CoreBundle/Repository/EntryRepository.php
+++ b/src/Wallabag/CoreBundle/Repository/EntryRepository.php
@@ -22,7 +22,7 @@ class EntryRepository extends EntityRepository
22 return $this->createQueryBuilder('e') 22 return $this->createQueryBuilder('e')
23 ->leftJoin('e.user', 'u') 23 ->leftJoin('e.user', 'u')
24 ->andWhere('u.id = :userId')->setParameter('userId', $userId) 24 ->andWhere('u.id = :userId')->setParameter('userId', $userId)
25 ->orderBy('e.id', 'desc') 25 ->orderBy('e.createdAt', 'desc')
26 ; 26 ;
27 } 27 }
28 28
diff --git a/src/Wallabag/CoreBundle/Resources/config/services.yml b/src/Wallabag/CoreBundle/Resources/config/services.yml
index 614488a6..90a2419e 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 714ced14..6ca7e459 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.da.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.da.yml
@@ -137,7 +137,7 @@ entry:
137 # starred: 'Starred entries' 137 # starred: 'Starred entries'
138 # archived: 'Archived entries' 138 # archived: 'Archived entries'
139 # filtered: 'Filtered entries' 139 # filtered: 'Filtered entries'
140 # filtered_tags: 'Filtered by tags' 140 # filtered_tags: 'Filtered by tags:'
141 # untagged: 'Untagged entries' 141 # untagged: 'Untagged entries'
142 list: 142 list:
143 # number_on_the_page: '{0} There is no entry.|{1} There is one entry.|]1,Inf[ There are %count% entries.' 143 # number_on_the_page: '{0} There is no entry.|{1} There is one entry.|]1,Inf[ There are %count% entries.'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.de.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.de.yml
index 57e49f84..8fd1d82a 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.de.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.de.yml
@@ -137,7 +137,7 @@ entry:
137 starred: 'Favorisierte Einträge' 137 starred: 'Favorisierte Einträge'
138 archived: 'Archivierte Einträge' 138 archived: 'Archivierte Einträge'
139 filtered: 'Gefilterte Einträge' 139 filtered: 'Gefilterte Einträge'
140 filtered_tags: 'Gefiltert nach Tags' 140 filtered_tags: 'Gefiltert nach Tags:'
141 untagged: 'Nicht getaggte Einträge' 141 untagged: 'Nicht getaggte Einträge'
142 list: 142 list:
143 number_on_the_page: '{0} Es gibt keine Einträge.|{1} Es gibt einen Eintrag.|]1,Inf[ Es gibt %count% Einträge.' 143 number_on_the_page: '{0} Es gibt keine Einträge.|{1} Es gibt einen Eintrag.|]1,Inf[ Es gibt %count% Einträge.'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.en.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.en.yml
index 4a59c75e..02f56535 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.en.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.en.yml
@@ -137,7 +137,7 @@ entry:
137 starred: 'Starred entries' 137 starred: 'Starred entries'
138 archived: 'Archived entries' 138 archived: 'Archived entries'
139 filtered: 'Filtered entries' 139 filtered: 'Filtered entries'
140 filtered_tags: 'Filtered by tags' 140 filtered_tags: 'Filtered by tags:'
141 untagged: 'Untagged entries' 141 untagged: 'Untagged entries'
142 list: 142 list:
143 number_on_the_page: '{0} There are no entries.|{1} There is one entry.|]1,Inf[ There are %count% entries.' 143 number_on_the_page: '{0} There are no entries.|{1} There is one entry.|]1,Inf[ There are %count% entries.'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.es.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.es.yml
index 1b1e0cb1..42ec8183 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.es.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.es.yml
@@ -137,7 +137,7 @@ entry:
137 starred: 'Artículos favoritos' 137 starred: 'Artículos favoritos'
138 archived: 'Artículos archivados' 138 archived: 'Artículos archivados'
139 filtered: 'Artículos filtrados' 139 filtered: 'Artículos filtrados'
140 # filtered_tags: 'Filtered by tags' 140 # filtered_tags: 'Filtered by tags:'
141 # untagged: 'Untagged entries' 141 # untagged: 'Untagged entries'
142 list: 142 list:
143 number_on_the_page: '{0} No hay artículos.|{1} Hay un artículo.|]1,Inf[ Hay %count% artículos.' 143 number_on_the_page: '{0} No hay artículos.|{1} Hay un artículo.|]1,Inf[ Hay %count% artículos.'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml
index 41dc8acf..f82167df 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml
@@ -137,7 +137,7 @@ entry:
137 starred: 'مقاله‌های برگزیده' 137 starred: 'مقاله‌های برگزیده'
138 archived: 'مقاله‌های بایگانی‌شده' 138 archived: 'مقاله‌های بایگانی‌شده'
139 filtered: 'مقاله‌های فیلترشده' 139 filtered: 'مقاله‌های فیلترشده'
140 # filtered_tags: 'Filtered by tags' 140 # filtered_tags: 'Filtered by tags:'
141 # untagged: 'Untagged entries' 141 # untagged: 'Untagged entries'
142 list: 142 list:
143 number_on_the_page: '{0} هیج مقاله‌ای نیست.|{1} یک مقاله هست.|]1,Inf[ %count% مقاله هست.' 143 number_on_the_page: '{0} هیج مقاله‌ای نیست.|{1} یک مقاله هست.|]1,Inf[ %count% مقاله هست.'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml
index 7fb9681d..421cb8b5 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml
@@ -1,422 +1,422 @@
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 form_rss: 74 form_rss:
75 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." 75 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 token_label: 'Jeton RSS' 76 token_label: "Jeton RSS"
77 no_token: 'Aucun jeton généré' 77 no_token: "Aucun jeton généré"
78 token_create: 'Créez votre jeton' 78 token_create: "Créez votre jeton"
79 token_reset: 'Réinitialisez votre jeton' 79 token_reset: "Réinitialisez votre jeton"
80 rss_links: 'URL de vos flux RSS' 80 rss_links: "Adresse de vos flux RSS"
81 rss_link: 81 rss_link:
82 unread: 'non lus' 82 unread: "non lus"
83 starred: 'favoris' 83 starred: "favoris"
84 archive: 'lus' 84 archive: "lus"
85 rss_limit: "Nombre d'articles dans le flux" 85 rss_limit: "Nombre darticles dans le flux"
86 form_user: 86 form_user:
87 two_factor_description: "Activer l'authentification double-facteur veut dire que vous allez recevoir un code par email à chaque nouvelle connexion non approuvée." 87 two_factor_description: "Activer l’authentification double-facteur veut dire que vous allez recevoir un code par courriel à chaque nouvelle connexion non approuvée."
88 name_label: 'Nom' 88 name_label: "Nom"
89 email_label: 'Adresse e-mail' 89 email_label: "Adresse courriel"
90 twoFactorAuthentication_label: 'Double authentification' 90 twoFactorAuthentication_label: "Double authentification"
91 form_password: 91 form_password:
92 old_password_label: 'Mot de passe actuel' 92 old_password_label: "Mot de passe actuel"
93 new_password_label: 'Nouveau mot de passe' 93 new_password_label: "Nouveau mot de passe"
94 repeat_new_password_label: 'Confirmez votre nouveau mot de passe' 94 repeat_new_password_label: "Confirmez votre nouveau mot de passe"
95 form_rules: 95 form_rules:
96 if_label: 'si' 96 if_label: "si"
97 then_tag_as_label: 'alors attribuer les tags' 97 then_tag_as_label: "alors attribuer les tags"
98 delete_rule_label: 'supprimer' 98 delete_rule_label: "supprimer"
99 edit_rule_label: 'éditer' 99 edit_rule_label: "éditer"
100 rule_label: 'Règle' 100 rule_label: "Règle"
101 tags_label: 'Tags' 101 tags_label: "Tags"
102 faq: 102 faq:
103 title: 'FAQ' 103 title: "FAQ"
104 tagging_rules_definition_title: 'Que signifient les règles de tag automatiques ?' 104 tagging_rules_definition_title: "Que signifient les règles de tag automatiques ?"
105 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." 105 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."
106 how_to_use_them_title: 'Comment les utiliser ?' 106 how_to_use_them_title: "Comment les utiliser ?"
107 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> »' 107 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> »"
108 variables_available_title: 'Quelles variables et opérateurs puis-je utiliser pour écrire des règles ?' 108 variables_available_title: "Quelles variables et opérateurs puis-je utiliser pour écrire des règles ?"
109 variables_available_description: 'Les variables et opérateurs suivants peuvent être utilisés pour écrire des règles de tag automatiques :' 109 variables_available_description: "Les variables et opérateurs suivants peuvent être utilisés pour écrire des règles de tag automatiques :"
110 meaning: 'Signification' 110 meaning: "Signification"
111 variable_description: 111 variable_description:
112 label: 'Variable' 112 label: "Variable"
113 title: "Titre de l'article" 113 title: "Titre de larticle"
114 url: "URL de l'article" 114 url: "Adresse de larticle"
115 isArchived: "Si l'article est archivé ou non" 115 isArchived: "Si larticle est archivé ou non"
116 isStarred: "Si l'article est favori ou non" 116 isStarred: "Si larticle est favori ou non"
117 content: "Le contenu de l'article" 117 content: "Le contenu de larticle"
118 language: "La langue de l'article" 118 language: "La langue de larticle"
119 mimetype: "Le type MIME de l'article" 119 mimetype: "Le type MIME de larticle"
120 readingTime: "Le temps de lecture estimé de l'article, en minutes" 120 readingTime: "Le temps de lecture estimé de larticle, en minutes"
121 domainName: "Le nom de domaine de l'article" 121 domainName: "Le nom de domaine de larticle"
122 operator_description: 122 operator_description:
123 label: 'Opérateur' 123 label: "Opérateur"
124 less_than: 'Moins que…...' 124 less_than: "Moins que…..."
125 strictly_less_than: 'Strictement moins que…' 125 strictly_less_than: "Strictement moins que…"
126 greater_than: 'Plus que…' 126 greater_than: "Plus que…"
127 strictly_greater_than: 'Strictement plus que…' 127 strictly_greater_than: "Strictement plus que…"
128 equal_to: 'Égal à…' 128 equal_to: "Égal à…"
129 not_equal_to: 'Différent de…' 129 not_equal_to: "Différent de…"
130 or: "Une règle OU l'autre" 130 or: "Une règle OU lautre"
131 and: "Une règle ET l'autre" 131 and: "Une règle ET lautre"
132 matches: 'Teste si un <i>sujet</i> correspond à une <i>recherche</i> (non sensible à la casse).<br />Exemple : <code>title matches "football"</code>' 132 matches: "Teste si un <i>sujet</i> correspond à une <i>recherche</i> (non sensible à la casse).<br />Exemple : <code>title matches \"football\"</code>"
133 133
134entry: 134entry:
135 page_titles: 135 page_titles:
136 unread: 'Articles non lus' 136 unread: "Articles non lus"
137 starred: 'Articles favoris' 137 starred: "Articles favoris"
138 archived: 'Articles lus' 138 archived: "Articles lus"
139 filtered: 'Articles filtrés' 139 filtered: "Articles filtrés"
140 filtered_tags: 'Articles filtrés par tags' 140 filtered_tags: "Articles filtrés par tags :"
141 untagged: 'Article sans tag' 141 untagged: "Article sans tag"
142 list: 142 list:
143 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." 143 number_on_the_page: "{0} Il ny a pas darticles.|{1} Il y a un article.|]1,Inf[ Il y a %count% articles."
144 reading_time: 'durée de lecture' 144 reading_time: "durée de lecture"
145 reading_time_minutes: 'durée de lecture: %readingTime% min' 145 reading_time_minutes: "durée de lecture: %readingTime% min"
146 reading_time_less_one_minute: 'durée de lecture: <small class="inferieur">&lt;</small> 1 min' 146 reading_time_less_one_minute: "durée de lecture: <small class=\"inferieur\">&lt;</small> 1 min"
147 number_of_tags: '{1}et un autre tag|]1,Inf[et %count% autres tags' 147 number_of_tags: "{1}et un autre tag|]1,Inf[et %count% autres tags"
148 reading_time_minutes_short: '%readingTime% min' 148 reading_time_minutes_short: "%readingTime% min"
149 reading_time_less_one_minute_short: '<small class="inferieur">&lt;</small> 1 min' 149 reading_time_less_one_minute_short: "<small class=\"inferieur\">&lt;</small> 1 min"
150 original_article: 'original' 150 original_article: "original"
151 toogle_as_read: 'Marquer comme lu/non lu' 151 toogle_as_read: "Marquer comme lu/non lu"
152 toogle_as_star: 'Marquer comme favori' 152 toogle_as_star: "Marquer comme favori"
153 delete: 'Supprimer' 153 delete: "Supprimer"
154 export_title: 'Exporter' 154 export_title: "Exporter"
155 filters: 155 filters:
156 title: 'Filtres' 156 title: "Filtres"
157 status_label: 'Status' 157 status_label: "Status"
158 archived_label: 'Lus' 158 archived_label: "Lus"
159 starred_label: 'Favoris' 159 starred_label: "Favoris"
160 unread_label: 'Non lus' 160 unread_label: "Non lus"
161 preview_picture_label: 'A une photo' 161 preview_picture_label: "A une photo"
162 preview_picture_help: 'Photo' 162 preview_picture_help: "Photo"
163 language_label: 'Langue' 163 language_label: "Langue"
164 reading_time: 164 reading_time:
165 label: 'Durée de lecture en minutes' 165 label: "Durée de lecture en minutes"
166 from: 'de' 166 from: "de"
167 to: 'à' 167 to: "à"
168 domain_label: 'Nom de domaine' 168 domain_label: "Nom de domaine"
169 created_at: 169 created_at:
170 label: 'Date de création' 170 label: "Date de création"
171 from: 'de' 171 from: "de"
172 to: 'à' 172 to: "à"
173 action: 173 action:
174 clear: 'Effacer' 174 clear: "Effacer"
175 filter: 'Filtrer' 175 filter: "Filtrer"
176 view: 176 view:
177 left_menu: 177 left_menu:
178 back_to_top: 'Revenir en haut' 178 back_to_top: "Revenir en haut"
179 back_to_homepage: 'Retour' 179 back_to_homepage: "Retour"
180 set_as_read: 'Marquer comme lu' 180 set_as_read: "Marquer comme lu"
181 set_as_unread: 'Marquer comme non lu' 181 set_as_unread: "Marquer comme non lu"
182 set_as_starred: 'Mettre en favori' 182 set_as_starred: "Mettre en favori"
183 view_original_article: 'Article original' 183 view_original_article: "Article original"
184 re_fetch_content: 'Recharger le contenu' 184 re_fetch_content: "Recharger le contenu"
185 delete: 'Supprimer' 185 delete: "Supprimer"
186 add_a_tag: 'Ajouter un tag' 186 add_a_tag: "Ajouter un tag"
187 share_content: 'Partager' 187 share_content: "Partager"
188 share_email_label: 'Email' 188 share_email_label: "Courriel"
189 public_link: 'Lien public' 189 public_link: "Lien public"
190 delete_public_link: 'Supprimer lien public' 190 delete_public_link: "Supprimer le lien public"
191 download: 'Télécharger' 191 download: "Télécharger"
192 print: 'Imprimer' 192 print: "Imprimer"
193 problem: 193 problem:
194 label: 'Un problème ?' 194 label: "Un problème ?"
195 description: "Est-ce que cet article s'affiche mal ?" 195 description: "Est-ce que cet article saffiche mal ?"
196 edit_title: 'Modifier le titre' 196 edit_title: "Modifier le titre"
197 original_article: 'original' 197 original_article: "original"
198 annotations_on_the_entry: '{0} Aucune annotation|{1} Une annotation|]1,Inf[ %count% annotations' 198 annotations_on_the_entry: "{0} Aucune annotation|{1} Une annotation|]1,Inf[ %count% annotations"
199 created_at: 'Date de création' 199 created_at: "Date de création"
200 new: 200 new:
201 page_title: 'Sauvegarder un nouvel article' 201 page_title: "Sauvegarder un nouvel article"
202 placeholder: 'http://website.com' 202 placeholder: "http://website.com"
203 form_new: 203 form_new:
204 url_label: Url 204 url_label: "Adresse"
205 edit: 205 edit:
206 page_title: 'Éditer un article' 206 page_title: "Éditer un article"
207 title_label: 'Titre' 207 title_label: "Titre"
208 url_label: 'Url' 208 url_label: "Adresse"
209 is_public_label: 'Public' 209 is_public_label: "Public"
210 save_label: 'Enregistrer' 210 save_label: "Enregistrer"
211 public: 211 public:
212 shared_by_wallabag: "Cet article a été partagé par <a href='%wallabag_instance%'>wallabag</a>" 212 shared_by_wallabag: "Cet article a été partagé par <a href=\"%wallabag_instance%\">wallabag</a>"
213 213
214about: 214about:
215 page_title: 'À propos' 215 page_title: "À propos"
216 top_menu: 216 top_menu:
217 who_behind_wallabag: "L'équipe derrière wallabag" 217 who_behind_wallabag: "Léquipe derrière wallabag"
218 getting_help: "Besoin d'aide" 218 getting_help: "Besoin daide"
219 helping: 'Aider wallabag' 219 helping: "Aider wallabag"
220 contributors: 'Contributeurs' 220 contributors: "Contributeurs"
221 third_party: 'Librairies tierces' 221 third_party: "Librairies tierces"
222 who_behind_wallabag: 222 who_behind_wallabag:
223 developped_by: 'Développé par' 223 developped_by: "Développé par"
224 website: 'Site web' 224 website: "Site web"
225 many_contributors: 'Et plein de contributeurs ♥ <a href="https://github.com/wallabag/wallabag/graphs/contributors">sur Github</a>' 225 many_contributors: "Et plein de contributeurs ♥ <a href=\"https://github.com/wallabag/wallabag/graphs/contributors\">sur Github</a>"
226 project_website: 'Site web du projet' 226 project_website: "Site web du projet"
227 license: 'Licence' 227 license: "Licence"
228 version: 'Version' 228 version: "Version"
229 getting_help: 229 getting_help:
230 documentation: 'Documentation' 230 documentation: "Documentation"
231 bug_reports: 'Rapport de bugs' 231 bug_reports: "Rapport de bogue"
232 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>' 232 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>"
233 helping: 233 helping:
234 description: 'wallabag est gratuit et opensource. Vous pouvez nous aider :' 234 description: "wallabag est gratuit et opensource. Vous pouvez nous aider :"
235 by_contributing: 'en contribuant au projet :' 235 by_contributing: "en contribuant au projet :"
236 by_contributing_2: 'un ticket recense tous nos besoins' 236 by_contributing_2: "un ticket recense tous nos besoins"
237 by_paypal: 'via Paypal' 237 by_paypal: "via Paypal"
238 contributors: 238 contributors:
239 description: "Merci aux contributeurs de l'application web de wallabag" 239 description: "Merci aux contributeurs de lapplication web de wallabag"
240 third_party: 240 third_party:
241 description: 'Voici la liste des dépendances utilisées dans wallabag (et leur license) :' 241 description: "Voici la liste des dépendances utilisées dans wallabag (et leur license) :"
242 package: 'Dépendance' 242 package: "Dépendance"
243 license: 'Licence' 243 license: "Licence"
244 244
245howto: 245howto:
246 page_title: 'Aide' 246 page_title: "Aide"
247 page_description: "Il y a plusieurs façon d'enregistrer un article :" 247 page_description: "Il y a plusieurs façon denregistrer un article :"
248 top_menu: 248 top_menu:
249 browser_addons: 'Extensions de navigateur' 249 browser_addons: "Extensions de navigateur"
250 mobile_apps: 'Applications smartphone' 250 mobile_apps: "Applications smartphone"
251 bookmarklet: 'Bookmarklet' 251 bookmarklet: "Bookmarklet"
252 form: 252 form:
253 description: 'Grâce à ce formulaire' 253 description: "Grâce à ce formulaire"
254 browser_addons: 254 browser_addons:
255 firefox: 'Extension Firefox' 255 firefox: "Extension Firefox"
256 chrome: 'Extension Chrome' 256 chrome: "Extension Chrome"
257 mobile_apps: 257 mobile_apps:
258 android: 258 android:
259 via_f_droid: 'via F-Droid' 259 via_f_droid: "via F-Droid"
260 via_google_play: 'via Google Play' 260 via_google_play: "via Google Play"
261 ios: 'sur iTunes Store' 261 ios: "sur iTunes Store"
262 windows: 'sur Microsoft Store' 262 windows: "sur Microsoft Store"
263 bookmarklet: 263 bookmarklet:
264 description: 'Glissez et déposez ce lien dans votre barre de favoris :' 264 description: "Glissez et déposez ce lien dans votre barre de favoris :"
265 265
266quickstart: 266quickstart:
267 page_title: 'Pour bien débuter' 267 page_title: "Pour bien débuter"
268 more: 'Et plus encore…' 268 more: "Et plus encore…"
269 intro: 269 intro:
270 title: 'Bienvenue sur wallabag !' 270 title: "Bienvenue sur wallabag !"
271 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." 271 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."
272 paragraph_2: 'Suivez-nous !' 272 paragraph_2: "Suivez-nous !"
273 configure: 273 configure:
274 title: "Configurez l'application" 274 title: "Configurez lapplication"
275 description: 'Pour voir une application qui vous correspond, allez voir du côté de la configuration de wallabag.' 275 description: "Pour voir une application qui vous correspond, allez voir du côté de la configuration de wallabag."
276 language: "Changez la langue et le design de l'application" 276 language: "Changez la langue et le design de lapplication"
277 rss: 'Activez les flux RSS' 277 rss: "Activez les flux RSS"
278 tagging_rules: 'Écrivez des règles pour classer automatiquement vos articles' 278 tagging_rules: "Écrivez des règles pour classer automatiquement vos articles"
279 admin: 279 admin:
280 title: 'Administration' 280 title: "Administration"
281 description: "En tant qu'administrateur sur wallabag, vous avez des privilèges qui vous permettent de :" 281 description: "En tant quadministrateur sur wallabag, vous avez des privilèges qui vous permettent de :"
282 new_user: 'Créer un nouvel utilisateur' 282 new_user: "Créer un nouvel utilisateur"
283 analytics: 'Configurer les statistiques' 283 analytics: "Configurer les statistiques"
284 sharing: 'Activer des paramètres de partages' 284 sharing: "Activer des paramètres de partages"
285 export: "Configurer les formats d'export" 285 export: "Configurer les formats dexport"
286 import: "Configurer l'import" 286 import: "Configurer limport"
287 first_steps: 287 first_steps:
288 title: 'Premiers pas' 288 title: "Premiers pas"
289 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." 289 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."
290 new_article: 'Ajoutez votre premier article' 290 new_article: "Ajoutez votre premier article"
291 unread_articles: 'Et rangez-le !' 291 unread_articles: "Et rangez-le !"
292 migrate: 292 migrate:
293 title: 'Migrer depuis un service existant' 293 title: "Migrer depuis un service existant"
294 description: "Vous êtes un ancien utilisateur d'un service existant ? Nous allons vous aider à récupérer vos données sur wallabag." 294 description: "Vous êtes un ancien utilisateur d’un service existant ? Nous allons vous aider à récupérer vos données sur wallabag."
295 pocket: 'Migrer depuis Pocket' 295 pocket: "Migrer depuis Pocket"
296 wallabag_v1: 'Migrer depuis wallabag v1' 296 wallabag_v1: "Migrer depuis wallabag v1"
297 wallabag_v2: 'Migrer depuis wallabag v2' 297 wallabag_v2: "Migrer depuis wallabag v2"
298 readability: 'Migrer depuis Readability' 298 readability: "Migrer depuis Readability"
299 instapaper: 'Migrer depuis Instapaper' 299 instapaper: "Migrer depuis Instapaper"
300 developer: 300 developer:
301 title: 'Pour les développeurs' 301 title: "Pour les développeurs"
302 description: 'Nous avons aussi pensé aux développeurs : Docker, API, traductions, etc.' 302 description: "Nous avons aussi pensé aux développeurs : Docker, API, traductions, etc."
303 create_application: 'Créer votre application tierce' 303 create_application: "Créer votre application tierce"
304 use_docker: 'Utiliser Docker pour installer wallabag' 304 use_docker: "Utiliser Docker pour installer wallabag"
305 docs: 305 docs:
306 title: 'Documentation complète' 306 title: "Documentation complète"
307 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." 307 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."
308 annotate: 'Annoter votre article' 308 annotate: "Annoter votre article"
309 export: 'Convertissez vos articles en ePub ou en PDF' 309 export: "Convertissez vos articles en ePub ou en PDF"
310 search_filters: "Apprenez à utiliser le moteur de recherche et les filtres pour retrouver l'article qui vous intéresse" 310 search_filters: "Apprenez à utiliser le moteur de recherche et les filtres pour retrouver l’article qui vous intéresse"
311 fetching_errors: "Que faire si mon article n'est pas correctement récupéré ?" 311 fetching_errors: "Que faire si mon article nest pas correctement récupéré ?"
312 all_docs: "Et encore plein d'autres choses !" 312 all_docs: "Et encore plein dautres choses !"
313 support: 313 support:
314 title: 'Support' 314 title: "Support"
315 description: 'Parce que vous avez peut-être besoin de nous poser une question, nous sommes disponibles pour vous.' 315 description: "Parce que vous avez peut-être besoin de nous poser une question, nous sommes disponibles pour vous."
316 github: 'Sur GitHub' 316 github: "Sur GitHub"
317 email: 'Par e-mail' 317 email: "Par courriel"
318 gitter: 'Sur Gitter' 318 gitter: "Sur Gitter"
319 319
320tag: 320tag:
321 page_title: 'Tags' 321 page_title: "Tags"
322 list: 322 list:
323 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." 323 number_on_the_page: "{0} Il ny a pas de tag.|{1} Il y a un tag.|]1,Inf[ Il y a %count% tags."
324 see_untagged_entries: 'Voir les articles sans tag' 324 see_untagged_entries: "Voir les articles sans tag"
325 325
326import: 326import:
327 page_title: 'Importer' 327 page_title: "Importer"
328 page_description: "Bienvenue dans l'outil de migration de wallabag. Choisissez ci-dessous le service depuis lequel vous souhaitez migrer." 328 page_description: "Bienvenue dans l’outil de migration de wallabag. Choisissez ci-dessous le service depuis lequel vous souhaitez migrer."
329 action: 329 action:
330 import_contents: 'Importer les contenus' 330 import_contents: "Importer les contenus"
331 form: 331 form:
332 mark_as_read_title: 'Marquer tout comme lu ?' 332 mark_as_read_title: "Marquer tout comme lu ?"
333 mark_as_read_label: 'Marquer tous les contenus importés comme lus' 333 mark_as_read_label: "Marquer tous les contenus importés comme lus"
334 file_label: 'Fichier' 334 file_label: "Fichier"
335 save_label: 'Importer le fichier' 335 save_label: "Importer le fichier"
336 pocket: 336 pocket:
337 page_title: 'Importer > Pocket' 337 page_title: "Importer > Pocket"
338 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." 338 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."
339 config_missing: 339 config_missing:
340 description: "L'import à partir de Pocket n'est pas configuré." 340 description: "Limport à partir de Pocket nest pas configuré."
341 admin_message: "Vous devez définir %keyurls%une clé pour l'API Pocket%keyurle%." 341 admin_message: "Vous devez définir %keyurls%une clé pour lAPI Pocket%keyurle%."
342 user_message: "L'administrateur de votre serveur doit définir une clé pour l'API Pocket." 342 user_message: "Ladministrateur de votre serveur doit définir une clé pour lAPI Pocket."
343 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." 343 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."
344 connect_to_pocket: 'Se connecter à Pocket et importer les données' 344 connect_to_pocket: "Se connecter à Pocket et importer les données"
345 wallabag_v1: 345 wallabag_v1:
346 page_title: 'Importer > Wallabag v1' 346 page_title: "Importer > wallabag v1"
347 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".' 347 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 »."
348 how_to: "Choisissez le fichier de votre export wallabag v1 et cliquez sur le bouton ci-dessous pour l'importer." 348 how_to: "Choisissez le fichier de votre export wallabag v1 et cliquez sur le bouton ci-dessous pour limporter."
349 wallabag_v2: 349 wallabag_v2:
350 page_title: 'Importer > Wallabag v2' 350 page_title: "Importer > wallabag v2"
351 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\"" 351 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 »"
352 readability: 352 readability:
353 page_title: 'Importer > Readability' 353 page_title: "Importer > Readability"
354 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.' 354 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."
355 how_to: "Choisissez le fichier de votre export Readability et cliquez sur le bouton ci-dessous pour l'importer." 355 how_to: "Choisissez le fichier de votre export Readability et cliquez sur le bouton ci-dessous pour limporter."
356 worker: 356 worker:
357 enabled: "Les imports sont asynchrones. Une fois l'import commencé un worker externe traitera les messages un par un. Le service activé est :" 357 enabled: "Les imports sont asynchrones. Une fois l’import commencé un worker externe traitera les messages un par un. Le service activé est :"
358 firefox: 358 firefox:
359 page_title: 'Import > Firefox' 359 page_title: "Import > Firefox"
360 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>" 360 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>"
361 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." 361 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."
362 chrome: 362 chrome:
363 page_title: 'Import > Chrome' 363 page_title: "Import > Chrome"
364 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>" 364 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>"
365 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." 365 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."
366 instapaper: 366 instapaper:
367 page_title: 'Import > Instapaper' 367 page_title: "Import > Instapaper"
368 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").' 368 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 »)."
369 how_to: "Choisissez le fichier de votre export Instapaper et cliquez sur le bouton ci-dessous pour l'importer." 369 how_to: "Choisissez le fichier de votre export Instapaper et cliquez sur le bouton ci-dessous pour limporter."
370 370
371developer: 371developer:
372 page_title: 'Développeur' 372 page_title: "Développeur"
373 welcome_message: "Bienvenue sur l'API de wallabag" 373 welcome_message: "Bienvenue sur lAPI de wallabag"
374 documentation: 'Documentation' 374 documentation: "Documentation"
375 how_to_first_app: 'Comment créer votre première application' 375 how_to_first_app: "Comment créer votre première application"
376 full_documentation: "Voir la documentation complète de l'API" 376 full_documentation: "Voir la documentation complète de lAPI"
377 list_methods: "Lister toutes les méthodes de l'API" 377 list_methods: "Lister toutes les méthodes de lAPI"
378 clients: 378 clients:
379 title: 'Clients' 379 title: "Clients"
380 create_new: 'Créer un nouveau client' 380 create_new: "Créer un nouveau client"
381 existing_clients: 381 existing_clients:
382 title: 'Les clients existants' 382 title: "Les clients existants"
383 field_id: 'ID Client' 383 field_id: "ID Client"
384 field_secret: 'Clé secrète' 384 field_secret: "Clé secrète"
385 field_uris: 'URLs de redirection' 385 field_uris: "Adresse de redirection"
386 field_grant_types: 'Type de privilège accordé' 386 field_grant_types: "Type de privilège accordé"
387 no_client: 'Aucun client pour le moment' 387 no_client: "Aucun client pour le moment"
388 remove: 388 remove:
389 warn_message_1: 'Vous avez la possibilité de supprimer le client %name%. Cette action est IRREVERSIBLE !' 389 warn_message_1: "Vous avez la possibilité de supprimer le client %name%. Cette action est IRRÉVERSIBLE !"
390 warn_message_2: "Si vous supprimez le client %name%, toutes les applications qui l'utilisaient ne fonctionneront plus avec votre compte wallabag." 390 warn_message_2: "Si vous supprimez le client %name%, toutes les applications qui l’utilisaient ne fonctionneront plus avec votre compte wallabag."
391 action: 'Supprimer le client %name%' 391 action: "Supprimer le client %name%"
392 client: 392 client:
393 page_title: 'Développeur > Nouveau client' 393 page_title: "Développeur > Nouveau client"
394 page_description: "Vous allez créer un nouveau client. Merci de remplir l'url de redirection vers votre application." 394 page_description: "Vous allez créer un nouveau client. Merci de remplir l’adresse de redirection vers votre application."
395 form: 395 form:
396 name_label: "Nom du client" 396 name_label: "Nom du client"
397 redirect_uris_label: 'URLs de redirection (optionnel)' 397 redirect_uris_label: "Adresses de redirection (optionnel)"
398 save_label: 'Créer un nouveau client' 398 save_label: "Créer un nouveau client"
399 action_back: 'Retour' 399 action_back: "Retour"
400 client_parameter: 400 client_parameter:
401 page_title: 'Développeur > Les paramètres de votre client' 401 page_title: "Développeur > Les paramètres de votre client"
402 page_description: 'Voilà les paramètres de votre client' 402 page_description: "Voilà les paramètres de votre client"
403 field_name: 'Nom du client' 403 field_name: "Nom du client"
404 field_id: 'ID Client' 404 field_id: "ID client"
405 field_secret: 'Clé secrète' 405 field_secret: "Clé secrète"
406 back: 'Retour' 406 back: "Retour"
407 read_howto: 'Lire "comment créer ma première application"' 407 read_howto: "Lire « comment créer ma première application »"
408 howto: 408 howto:
409 page_title: 'Développeur > Comment créer votre première application' 409 page_title: "Développeur > Comment créer votre première application"
410 description: 410 description:
411 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." 411 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."
412 paragraph_2: "Vous avez besoin d'un token pour échanger entre votre application et l'API de wallabag." 412 paragraph_2: "Vous avez besoin dun token pour échanger entre votre application et lAPI de wallabag."
413 paragraph_3: 'Pour créer un token, vous devez <a href="%link%">créer un nouveau client</a>.' 413 paragraph_3: "Pour créer un token, vous devez <a href=\"%link%\">créer un nouveau client</a>."
414 paragraph_4: 'Maintenant créez votre token (remplacer client_id, client_secret, username et password avec les bonnes valeurs):' 414 paragraph_4: "Maintenant créez votre token (remplacer client_id, client_secret, username et password avec les bonnes valeurs):"
415 paragraph_5: "L'API vous retournera une réponse comme ça :" 415 paragraph_5: "LAPI vous retournera une réponse comme ça :"
416 paragraph_6: "L'access_token doit être utilisé pour faire un appel à l'API. Par exemple :" 416 paragraph_6: "Laccess_token doit être utilisé pour faire un appel à lAPI. Par exemple :"
417 paragraph_7: "Cet appel va retourner tous les articles de l'utilisateur." 417 paragraph_7: "Cet appel va retourner tous les articles de lutilisateur."
418 paragraph_8: "Si vous voulez toutes les méthodes de l'API, jetez un oeil <a href=\"%link%\">à la documentation de l'API</a>." 418 paragraph_8: "Si vous voulez toutes les méthodes de l’API, jetez un oeil <a href=\"%link%\">à la documentation de l’API</a>."
419 back: 'Retour' 419 back: "Retour"
420 420
421user: 421user:
422 page_title: Gestion des utilisateurs 422 page_title: Gestion des utilisateurs
@@ -430,20 +430,20 @@ user:
430 no: Non 430 no: Non
431 create_new_one: Créer un nouvel utilisateur 431 create_new_one: Créer un nouvel utilisateur
432 form: 432 form:
433 username_label: "Nom d'utilisateur" 433 username_label: "Nom dutilisateur"
434 name_label: 'Nom' 434 name_label: "Nom"
435 password_label: 'Mot de passe' 435 password_label: "Mot de passe"
436 repeat_new_password_label: 'Confirmez votre nouveau mot de passe' 436 repeat_new_password_label: "Confirmez votre nouveau mot de passe"
437 plain_password_label: 'Mot de passe en clair' 437 plain_password_label: "Mot de passe en clair"
438 email_label: 'Adresse e-mail' 438 email_label: "Adresse courriel"
439 enabled_label: 'Activé' 439 enabled_label: "Activé"
440 locked_label: 'Bloqué' 440 locked_label: "Bloqué"
441 last_login_label: 'Dernière connexion' 441 last_login_label: "Dernière connexion"
442 twofactor_label: Double authentification 442 twofactor_label: "Double authentification"
443 save: Sauvegarder 443 save: "Sauvegarder"
444 delete: Supprimer 444 delete: "Supprimer"
445 delete_confirm: Êtes-vous r? 445 delete_confirm: "Voulez-vous vraiment ?"
446 back_to_list: Revenir à la liste 446 back_to_list: "Revenir à la liste"
447 447
448error: 448error:
449 page_title: Une erreur est survenue 449 page_title: Une erreur est survenue
@@ -451,41 +451,41 @@ error:
451flashes: 451flashes:
452 config: 452 config:
453 notice: 453 notice:
454 config_saved: 'Les paramètres ont bien été mis à jour. Certains seront pris en compte après déconnexion.' 454 config_saved: "Les paramètres ont bien été mis à jour. Certains seront pris en compte après déconnexion."
455 password_updated: 'Votre mot de passe a bien été mis à jour' 455 password_updated: "Votre mot de passe a bien été mis à jour"
456 password_not_updated_demo: "En démo, vous ne pouvez pas changer le mot de passe de cet utilisateur." 456 password_not_updated_demo: "En démo, vous ne pouvez pas changer le mot de passe de cet utilisateur."
457 user_updated: 'Vos informations personnelles ont bien été mises à jour' 457 user_updated: "Vos informations personnelles ont bien été mises à jour"
458 rss_updated: 'La configuration des flux RSS a bien été mise à jour' 458 rss_updated: "La configuration des flux RSS a bien été mise à jour"
459 tagging_rules_updated: 'Règles mises à jour' 459 tagging_rules_updated: "Règles mises à jour"
460 tagging_rules_deleted: 'Règle supprimée' 460 tagging_rules_deleted: "Règle supprimée"
461 user_added: 'Utilisateur "%username%" ajouté' 461 user_added: "Utilisateur \"%username%\" ajouté"
462 rss_token_updated: 'Jeton RSS mis à jour' 462 rss_token_updated: "Jeton RSS mis à jour"
463 entry: 463 entry:
464 notice: 464 notice:
465 entry_already_saved: 'Article déjà sauvergardé le %date%' 465 entry_already_saved: "Article déjà sauvergardé le %date%"
466 entry_saved: 'Article enregistré' 466 entry_saved: "Article enregistré"
467 entry_saved_failed: 'Article enregistré mais impossible de récupérer le contenu' 467 entry_saved_failed: "Article enregistré mais impossible de récupérer le contenu"
468 entry_updated: 'Article mis à jour' 468 entry_updated: "Article mis à jour"
469 entry_reloaded: 'Article rechargé' 469 entry_reloaded: "Article rechargé"
470 entry_reloaded_failed: "Article mis à jour mais impossible de récupérer le contenu" 470 entry_reloaded_failed: "Article mis à jour mais impossible de récupérer le contenu"
471 entry_archived: 'Article marqué comme lu' 471 entry_archived: "Article marqué comme lu"
472 entry_unarchived: 'Article marqué comme non lu' 472 entry_unarchived: "Article marqué comme non lu"
473 entry_starred: 'Article ajouté dans les favoris' 473 entry_starred: "Article ajouté dans les favoris"
474 entry_unstarred: 'Article retiré des favoris' 474 entry_unstarred: "Article retiré des favoris"
475 entry_deleted: 'Article supprimé' 475 entry_deleted: "Article supprimé"
476 tag: 476 tag:
477 notice: 477 notice:
478 tag_added: 'Tag ajouté' 478 tag_added: "Tag ajouté"
479 import: 479 import:
480 notice: 480 notice:
481 failed: "L'import a échoué, veuillez ré-essayer" 481 failed: "Limport a échoué, veuillez ré-essayer"
482 failed_on_file: "Erreur lors du traitement de l'import. Vérifier votre fichier." 482 failed_on_file: "Erreur lors du traitement de limport. Vérifiez votre fichier."
483 summary: "Rapport d'import: %imported% importés, %skipped% déjà présents." 483 summary: "Rapport dimport : %imported% importés, %skipped% déjà présents."
484 summary_with_queue: "Rapport d'import: %queued% en cours de traitement." 484 summary_with_queue: "Rapport dimport: %queued% en cours de traitement."
485 error: 485 error:
486 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. 486 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."
487 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. 487 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."
488 developer: 488 developer:
489 notice: 489 notice:
490 client_created: 'Nouveau client %name% créé' 490 client_created: "Nouveau client %name% créé"
491 client_deleted: 'Client %name% supprimé' 491 client_deleted: "Client %name% supprimé"
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.it.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.it.yml
index b279ae40..d679ef00 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.it.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.it.yml
@@ -137,7 +137,7 @@ entry:
137 starred: 'Contenuti preferiti' 137 starred: 'Contenuti preferiti'
138 archived: 'Contenuti archiviati' 138 archived: 'Contenuti archiviati'
139 filtered: 'Contenuti filtrati' 139 filtered: 'Contenuti filtrati'
140 # filtered_tags: 'Filtered by tags' 140 # filtered_tags: 'Filtered by tags:'
141 # untagged: 'Untagged entries' 141 # untagged: 'Untagged entries'
142 list: 142 list:
143 number_on_the_page: "{0} Non ci sono contenuti.|{1} C'è un contenuto.|]1,Inf[ Ci sono %count% contenuti." 143 number_on_the_page: "{0} Non ci sono contenuti.|{1} C'è un contenuto.|]1,Inf[ Ci sono %count% contenuti."
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml
index a4659620..af0fba0d 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml
@@ -137,7 +137,7 @@ entry:
137 starred: 'Articles favorits' 137 starred: 'Articles favorits'
138 archived: 'Articles legits' 138 archived: 'Articles legits'
139 filtered: 'Articles filtrats' 139 filtered: 'Articles filtrats'
140 filtered_tags: 'Filtats per etiquetas' 140 filtered_tags: 'Filtats per etiquetas:'
141 untagged: 'Articles sens etiqueta' 141 untagged: 'Articles sens etiqueta'
142 list: 142 list:
143 number_on_the_page: "{0} I a pas cap d'article.|{1} I a un article.|]1,Inf[ I a %count% articles." 143 number_on_the_page: "{0} I a pas cap d'article.|{1} I a un article.|]1,Inf[ I a %count% articles."
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml
index 798b39c2..bf47b58a 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml
@@ -137,7 +137,7 @@ entry:
137 starred: 'Wpisy oznaczone gwiazdką' 137 starred: 'Wpisy oznaczone gwiazdką'
138 archived: 'Zarchiwizowane wpisy' 138 archived: 'Zarchiwizowane wpisy'
139 filtered: 'Odfiltrowane wpisy' 139 filtered: 'Odfiltrowane wpisy'
140 filtered_tags: 'Filtrowane po tagach' 140 filtered_tags: 'Filtrowane po tagach:'
141 untagged: 'Odtaguj wpisy' 141 untagged: 'Odtaguj wpisy'
142 list: 142 list:
143 number_on_the_page: '{0} Nie ma wpisów.|{1} Jest jeden wpis.|]1,Inf[ Są %count% wpisy.' 143 number_on_the_page: '{0} Nie ma wpisów.|{1} Jest jeden wpis.|]1,Inf[ Są %count% wpisy.'
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..f10dc9aa
--- /dev/null
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.pt.yml
@@ -0,0 +1,492 @@
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 user_updated: 'Informação atualizada'
455 rss_updated: 'Informação de RSS atualizada'
456 tagging_rules_updated: 'Regras de tags atualizadas'
457 tagging_rules_deleted: 'Regra de tag apagada'
458 rss_token_updated: 'Token RSS atualizado'
459 entry:
460 notice:
461 entry_already_saved: 'Entrada já foi salva em %date%'
462 entry_saved: 'Entrada salva'
463 entry_saved_failed: 'Failed to save entry'
464 entry_updated: 'Entrada atualizada'
465 entry_reloaded: 'Entrada recarregada'
466 entry_reloaded_failed: 'Falha em recarregar a entrada'
467 entry_archived: 'Entrada arquivada'
468 entry_unarchived: 'Entrada desarquivada'
469 entry_starred: 'Entrada destacada'
470 entry_unstarred: 'Entrada não destacada'
471 entry_deleted: 'Entrada apagada'
472 tag:
473 notice:
474 tag_added: 'Tag adicionada'
475 import:
476 notice:
477 failed: 'Importação falhou, por favor tente novamente.'
478 failed_on_file: 'Erro ao processar a importação. Por favor verifique seu arquivo de importação.'
479 summary: 'relatório de importação: %imported% importados, %skipped% já existem.'
480 summary_with_queue: 'Importar sumáario: %queued% agendados.'
481 error:
482 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.'
483 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.'
484 developer:
485 notice:
486 client_created: 'Novo cliente criado.'
487 client_deleted: 'Cliente removido'
488 user:
489 notice:
490 added: 'Usuário "%username%" adicionado'
491 updated: 'Usuário "%username%" atualizado'
492 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 21f27e08..875c82e8 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml
@@ -137,7 +137,7 @@ entry:
137 # starred: 'Starred entries' 137 # starred: 'Starred entries'
138 # archived: 'Archived entries' 138 # archived: 'Archived entries'
139 # filtered: 'Filtered entries' 139 # filtered: 'Filtered entries'
140 # filtered_tags: 'Filtered by tags' 140 # filtered_tags: 'Filtered by tags:'
141 # untagged: 'Untagged entries' 141 # untagged: 'Untagged entries'
142 list: 142 list:
143 # number_on_the_page: '{0} There is no entry.|{1} There is one entry.|]1,Inf[ There are %count% entries.' 143 # number_on_the_page: '{0} There is no entry.|{1} There is one entry.|]1,Inf[ There are %count% entries.'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml
index f137ec99..f50f629a 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml
@@ -136,7 +136,7 @@ entry:
136 # starred: 'Starred entries' 136 # starred: 'Starred entries'
137 # archived: 'Archived entries' 137 # archived: 'Archived entries'
138 # filtered: 'Filtered entries' 138 # filtered: 'Filtered entries'
139 # filtered_tags: 'Filtered by tags' 139 # filtered_tags: 'Filtered by tags:'
140 # untagged: 'Untagged entries' 140 # untagged: 'Untagged entries'
141 list: 141 list:
142 number_on_the_page: '{0} Herhangi bir makale yok.|{1} Burada bir adet makale var.|]1,Inf[ Burada %count% adet makale var.' 142 number_on_the_page: '{0} Herhangi bir makale yok.|{1} Burada bir adet makale var.|]1,Inf[ Burada %count% adet makale var.'
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 cc150cf1..0f1c010f 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 %}
@@ -63,19 +67,23 @@
63 <!-- Export --> 67 <!-- Export -->
64 <aside id="download-form"> 68 <aside id="download-form">
65 {% set currentRoute = app.request.attributes.get('_route') %} 69 {% set currentRoute = app.request.attributes.get('_route') %}
70 {% set currentTag = '' %}
71 {% if tag is defined %}
72 {% set currentTag = tag %}
73 {% endif %}
66 {% if currentRoute == 'homepage' %} 74 {% if currentRoute == 'homepage' %}
67 {% set currentRoute = 'unread' %} 75 {% set currentRoute = 'unread' %}
68 {% endif %} 76 {% endif %}
69 <h2>{{ 'entry.list.export_title'|trans }}</h2> 77 <h2>{{ 'entry.list.export_title'|trans }}</h2>
70 <a href="javascript: void(null);" id="download-form-close" class="close-button--popup close-button">&times;</a> 78 <a href="javascript: void(null);" id="download-form-close" class="close-button--popup close-button">&times;</a>
71 <ul> 79 <ul>
72 {% if craue_setting('export_epub') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'epub' }) }}">EPUB</a></li>{% endif %} 80 {% 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 %}
73 {% if craue_setting('export_mobi') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'mobi' }) }}">MOBI</a></li>{% endif %} 81 {% 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 %}
74 {% if craue_setting('export_pdf') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'pdf' }) }}">PDF</a></li>{% endif %} 82 {% 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 %}
75 {% if craue_setting('export_csv') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'json' }) }}">JSON</a></li>{% endif %} 83 {% 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 %}
76 {% if craue_setting('export_json') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'csv' }) }}">CSV</a></li>{% endif %} 84 {% 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 %}
77 {% if craue_setting('export_txt') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'txt' }) }}">TXT</a></li>{% endif %} 85 {% 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 %}
78 {% if craue_setting('export_xml') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'xml' }) }}">XML</a></li>{% endif %} 86 {% 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 %}
79 </ul> 87 </ul>
80 </aside> 88 </aside>
81 89
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 1f88f774..6347afac 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 %}
@@ -97,18 +101,22 @@
97 <!-- Export --> 101 <!-- Export -->
98 <div id="export" class="side-nav fixed right-aligned"> 102 <div id="export" class="side-nav fixed right-aligned">
99 {% set currentRoute = app.request.attributes.get('_route') %} 103 {% set currentRoute = app.request.attributes.get('_route') %}
104 {% set currentTag = '' %}
105 {% if tag is defined %}
106 {% set currentTag = tag %}
107 {% endif %}
100 {% if currentRoute == 'homepage' %} 108 {% if currentRoute == 'homepage' %}
101 {% set currentRoute = 'unread' %} 109 {% set currentRoute = 'unread' %}
102 {% endif %} 110 {% endif %}
103 <h4 class="center">{{ 'entry.list.export_title'|trans }}</h4> 111 <h4 class="center">{{ 'entry.list.export_title'|trans }}</h4>
104 <ul> 112 <ul>
105 {% if craue_setting('export_epub') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'epub' }) }}">EPUB</a></li>{% endif %} 113 {% 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 %}
106 {% if craue_setting('export_mobi') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'mobi' }) }}">MOBI</a></li>{% endif %} 114 {% 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 %}
107 {% if craue_setting('export_pdf') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'pdf' }) }}">PDF</a></li>{% endif %} 115 {% 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 %}
108 {% if craue_setting('export_csv') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'json' }) }}">JSON</a></li>{% endif %} 116 {% 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 %}
109 {% if craue_setting('export_json') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'csv' }) }}">CSV</a></li>{% endif %} 117 {% 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 %}
110 {% if craue_setting('export_txt') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'txt' }) }}">TXT</a></li>{% endif %} 118 {% 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 %}
111 {% if craue_setting('export_xml') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'xml' }) }}">XML</a></li>{% endif %} 119 {% 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 %}
112 </ul> 120 </ul>
113 </div> 121 </div>
114 122
diff --git a/src/Wallabag/ImportBundle/Command/ImportCommand.php b/src/Wallabag/ImportBundle/Command/ImportCommand.php
index 1df38295..d1325338 100644
--- a/src/Wallabag/ImportBundle/Command/ImportCommand.php
+++ b/src/Wallabag/ImportBundle/Command/ImportCommand.php
@@ -50,6 +50,9 @@ class ImportCommand extends ContainerAwareCommand
50 case 'chrome': 50 case 'chrome':
51 $wallabag = $this->getContainer()->get('wallabag_import.chrome.import'); 51 $wallabag = $this->getContainer()->get('wallabag_import.chrome.import');
52 break; 52 break;
53 case 'instapaper':
54 $wallabag = $this->getContainer()->get('wallabag_import.instapaper.import');
55 break;
53 case 'v1': 56 case 'v1':
54 default: 57 default:
55 $wallabag = $this->getContainer()->get('wallabag_import.wallabag_v1.import'); 58 $wallabag = $this->getContainer()->get('wallabag_import.wallabag_v1.import');
diff --git a/src/Wallabag/ImportBundle/Resources/config/services.yml b/src/Wallabag/ImportBundle/Resources/config/services.yml
index 89adc71b..d600be0f 100644
--- a/src/Wallabag/ImportBundle/Resources/config/services.yml
+++ b/src/Wallabag/ImportBundle/Resources/config/services.yml
@@ -20,7 +20,6 @@ services:
20 arguments: 20 arguments:
21 - "@doctrine.orm.entity_manager" 21 - "@doctrine.orm.entity_manager"
22 - "@wallabag_core.content_proxy" 22 - "@wallabag_core.content_proxy"
23 - "@craue_config"
24 calls: 23 calls:
25 - [ setClient, [ "@wallabag_import.pocket.client" ] ] 24 - [ setClient, [ "@wallabag_import.pocket.client" ] ]
26 - [ setLogger, [ "@logger" ]] 25 - [ setLogger, [ "@logger" ]]
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 eb9c8e67..a8ee721b 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..825f8f7a
--- /dev/null
+++ b/tests/Wallabag/ApiBundle/Controller/EntryRestControllerTest.php
@@ -0,0 +1,673 @@
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->assertTrue(
34 $this->client->getResponse()->headers->contains(
35 'Content-Type',
36 'application/json'
37 )
38 );
39 }
40
41 public function testGetOneEntryWrongUser()
42 {
43 $entry = $this->client->getContainer()
44 ->get('doctrine.orm.entity_manager')
45 ->getRepository('WallabagCoreBundle:Entry')
46 ->findOneBy(['user' => 2, 'isArchived' => false]);
47
48 if (!$entry) {
49 $this->markTestSkipped('No content found in db.');
50 }
51
52 $this->client->request('GET', '/api/entries/'.$entry->getId().'.json');
53
54 $this->assertEquals(403, $this->client->getResponse()->getStatusCode());
55 }
56
57 public function testGetEntries()
58 {
59 $this->client->request('GET', '/api/entries');
60
61 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
62
63 $content = json_decode($this->client->getResponse()->getContent(), true);
64
65 $this->assertGreaterThanOrEqual(1, count($content));
66 $this->assertNotEmpty($content['_embedded']['items']);
67 $this->assertGreaterThanOrEqual(1, $content['total']);
68 $this->assertEquals(1, $content['page']);
69 $this->assertGreaterThanOrEqual(1, $content['pages']);
70
71 $this->assertTrue(
72 $this->client->getResponse()->headers->contains(
73 'Content-Type',
74 'application/json'
75 )
76 );
77 }
78
79 public function testGetEntriesWithFullOptions()
80 {
81 $this->client->request('GET', '/api/entries', [
82 'archive' => 1,
83 'starred' => 1,
84 'sort' => 'updated',
85 'order' => 'asc',
86 'page' => 1,
87 'perPage' => 2,
88 'tags' => 'foo',
89 'since' => 1443274283,
90 ]);
91
92 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
93
94 $content = json_decode($this->client->getResponse()->getContent(), true);
95
96 $this->assertGreaterThanOrEqual(1, count($content));
97 $this->assertArrayHasKey('items', $content['_embedded']);
98 $this->assertGreaterThanOrEqual(0, $content['total']);
99 $this->assertEquals(1, $content['page']);
100 $this->assertEquals(2, $content['limit']);
101 $this->assertGreaterThanOrEqual(1, $content['pages']);
102
103 $this->assertArrayHasKey('_links', $content);
104 $this->assertArrayHasKey('self', $content['_links']);
105 $this->assertArrayHasKey('first', $content['_links']);
106 $this->assertArrayHasKey('last', $content['_links']);
107
108 foreach (['self', 'first', 'last'] as $link) {
109 $this->assertArrayHasKey('href', $content['_links'][$link]);
110 $this->assertContains('archive=1', $content['_links'][$link]['href']);
111 $this->assertContains('starred=1', $content['_links'][$link]['href']);
112 $this->assertContains('sort=updated', $content['_links'][$link]['href']);
113 $this->assertContains('order=asc', $content['_links'][$link]['href']);
114 $this->assertContains('tags=foo', $content['_links'][$link]['href']);
115 $this->assertContains('since=1443274283', $content['_links'][$link]['href']);
116 }
117
118 $this->assertTrue(
119 $this->client->getResponse()->headers->contains(
120 'Content-Type',
121 'application/json'
122 )
123 );
124 }
125
126 public function testGetStarredEntries()
127 {
128 $this->client->request('GET', '/api/entries', ['starred' => 1, 'sort' => 'updated']);
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->assertNotEmpty($content['_embedded']['items']);
136 $this->assertGreaterThanOrEqual(1, $content['total']);
137 $this->assertEquals(1, $content['page']);
138 $this->assertGreaterThanOrEqual(1, $content['pages']);
139
140 $this->assertArrayHasKey('_links', $content);
141 $this->assertArrayHasKey('self', $content['_links']);
142 $this->assertArrayHasKey('first', $content['_links']);
143 $this->assertArrayHasKey('last', $content['_links']);
144
145 foreach (['self', 'first', 'last'] as $link) {
146 $this->assertArrayHasKey('href', $content['_links'][$link]);
147 $this->assertContains('starred=1', $content['_links'][$link]['href']);
148 $this->assertContains('sort=updated', $content['_links'][$link]['href']);
149 }
150
151 $this->assertTrue(
152 $this->client->getResponse()->headers->contains(
153 'Content-Type',
154 'application/json'
155 )
156 );
157 }
158
159 public function testGetArchiveEntries()
160 {
161 $this->client->request('GET', '/api/entries', ['archive' => 1]);
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('archive=1', $content['_links'][$link]['href']);
181 }
182
183 $this->assertTrue(
184 $this->client->getResponse()->headers->contains(
185 'Content-Type',
186 'application/json'
187 )
188 );
189 }
190
191 public function testGetTaggedEntries()
192 {
193 $this->client->request('GET', '/api/entries', ['tags' => 'foo,bar']);
194
195 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
196
197 $content = json_decode($this->client->getResponse()->getContent(), true);
198
199 $this->assertGreaterThanOrEqual(1, count($content));
200 $this->assertNotEmpty($content['_embedded']['items']);
201 $this->assertGreaterThanOrEqual(1, $content['total']);
202 $this->assertEquals(1, $content['page']);
203 $this->assertGreaterThanOrEqual(1, $content['pages']);
204
205 $this->assertArrayHasKey('_links', $content);
206 $this->assertArrayHasKey('self', $content['_links']);
207 $this->assertArrayHasKey('first', $content['_links']);
208 $this->assertArrayHasKey('last', $content['_links']);
209
210 foreach (['self', 'first', 'last'] as $link) {
211 $this->assertArrayHasKey('href', $content['_links'][$link]);
212 $this->assertContains('tags='.urlencode('foo,bar'), $content['_links'][$link]['href']);
213 }
214
215 $this->assertTrue(
216 $this->client->getResponse()->headers->contains(
217 'Content-Type',
218 'application/json'
219 )
220 );
221 }
222
223 public function testGetDatedEntries()
224 {
225 $this->client->request('GET', '/api/entries', ['since' => 1443274283]);
226
227 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
228
229 $content = json_decode($this->client->getResponse()->getContent(), true);
230
231 $this->assertGreaterThanOrEqual(1, count($content));
232 $this->assertNotEmpty($content['_embedded']['items']);
233 $this->assertGreaterThanOrEqual(1, $content['total']);
234 $this->assertEquals(1, $content['page']);
235 $this->assertGreaterThanOrEqual(1, $content['pages']);
236
237 $this->assertArrayHasKey('_links', $content);
238 $this->assertArrayHasKey('self', $content['_links']);
239 $this->assertArrayHasKey('first', $content['_links']);
240 $this->assertArrayHasKey('last', $content['_links']);
241
242 foreach (['self', 'first', 'last'] as $link) {
243 $this->assertArrayHasKey('href', $content['_links'][$link]);
244 $this->assertContains('since=1443274283', $content['_links'][$link]['href']);
245 }
246
247 $this->assertTrue(
248 $this->client->getResponse()->headers->contains(
249 'Content-Type',
250 'application/json'
251 )
252 );
253 }
254
255 public function testGetDatedSupEntries()
256 {
257 $future = new \DateTime(date('Y-m-d H:i:s'));
258 $this->client->request('GET', '/api/entries', ['since' => $future->getTimestamp() + 1000]);
259
260 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
261
262 $content = json_decode($this->client->getResponse()->getContent(), true);
263
264 $this->assertGreaterThanOrEqual(1, count($content));
265 $this->assertEmpty($content['_embedded']['items']);
266 $this->assertEquals(0, $content['total']);
267 $this->assertEquals(1, $content['page']);
268 $this->assertEquals(1, $content['pages']);
269
270 $this->assertArrayHasKey('_links', $content);
271 $this->assertArrayHasKey('self', $content['_links']);
272 $this->assertArrayHasKey('first', $content['_links']);
273 $this->assertArrayHasKey('last', $content['_links']);
274
275 foreach (['self', 'first', 'last'] as $link) {
276 $this->assertArrayHasKey('href', $content['_links'][$link]);
277 $this->assertContains('since='.($future->getTimestamp() + 1000), $content['_links'][$link]['href']);
278 }
279
280 $this->assertTrue(
281 $this->client->getResponse()->headers->contains(
282 'Content-Type',
283 'application/json'
284 )
285 );
286 }
287
288 public function testDeleteEntry()
289 {
290 $entry = $this->client->getContainer()
291 ->get('doctrine.orm.entity_manager')
292 ->getRepository('WallabagCoreBundle:Entry')
293 ->findOneByUser(1);
294
295 if (!$entry) {
296 $this->markTestSkipped('No content found in db.');
297 }
298
299 $this->client->request('DELETE', '/api/entries/'.$entry->getId().'.json');
300
301 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
302
303 $content = json_decode($this->client->getResponse()->getContent(), true);
304
305 $this->assertEquals($entry->getTitle(), $content['title']);
306 $this->assertEquals($entry->getUrl(), $content['url']);
307
308 // We'll try to delete this entry again
309 $this->client->request('DELETE', '/api/entries/'.$entry->getId().'.json');
310
311 $this->assertEquals(404, $this->client->getResponse()->getStatusCode());
312 }
313
314 public function testPostEntry()
315 {
316 $this->client->request('POST', '/api/entries.json', [
317 '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',
318 'tags' => 'google',
319 'title' => 'New title for my article',
320 ]);
321
322 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
323
324 $content = json_decode($this->client->getResponse()->getContent(), true);
325
326 $this->assertGreaterThan(0, $content['id']);
327 $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']);
328 $this->assertEquals(false, $content['is_archived']);
329 $this->assertEquals(false, $content['is_starred']);
330 $this->assertEquals('New title for my article', $content['title']);
331 $this->assertEquals(1, $content['user_id']);
332 $this->assertCount(1, $content['tags']);
333 }
334
335 public function testPostSameEntry()
336 {
337 $this->client->request('POST', '/api/entries.json', [
338 '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',
339 'archive' => '1',
340 'tags' => 'google, apple',
341 ]);
342
343 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
344
345 $content = json_decode($this->client->getResponse()->getContent(), true);
346
347 $this->assertGreaterThan(0, $content['id']);
348 $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']);
349 $this->assertEquals(true, $content['is_archived']);
350 $this->assertEquals(false, $content['is_starred']);
351 $this->assertCount(2, $content['tags']);
352 }
353
354 public function testPostArchivedAndStarredEntry()
355 {
356 $this->client->request('POST', '/api/entries.json', [
357 'url' => 'http://www.lemonde.fr/idees/article/2016/02/08/preserver-la-liberte-d-expression-sur-les-reseaux-sociaux_4861503_3232.html',
358 'archive' => '1',
359 'starred' => '1',
360 ]);
361
362 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
363
364 $content = json_decode($this->client->getResponse()->getContent(), true);
365
366 $this->assertGreaterThan(0, $content['id']);
367 $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']);
368 $this->assertEquals(true, $content['is_archived']);
369 $this->assertEquals(true, $content['is_starred']);
370 $this->assertEquals(1, $content['user_id']);
371 }
372
373 public function testPostArchivedAndStarredEntryWithoutQuotes()
374 {
375 $this->client->request('POST', '/api/entries.json', [
376 'url' => 'http://www.lemonde.fr/idees/article/2016/02/08/preserver-la-liberte-d-expression-sur-les-reseaux-sociaux_4861503_3232.html',
377 'archive' => 0,
378 'starred' => 1,
379 ]);
380
381 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
382
383 $content = json_decode($this->client->getResponse()->getContent(), true);
384
385 $this->assertGreaterThan(0, $content['id']);
386 $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']);
387 $this->assertEquals(false, $content['is_archived']);
388 $this->assertEquals(true, $content['is_starred']);
389 }
390
391 public function testPatchEntry()
392 {
393 $entry = $this->client->getContainer()
394 ->get('doctrine.orm.entity_manager')
395 ->getRepository('WallabagCoreBundle:Entry')
396 ->findOneByUser(1);
397
398 if (!$entry) {
399 $this->markTestSkipped('No content found in db.');
400 }
401
402 // hydrate the tags relations
403 $nbTags = count($entry->getTags());
404
405 $this->client->request('PATCH', '/api/entries/'.$entry->getId().'.json', [
406 'title' => 'New awesome title',
407 'tags' => 'new tag '.uniqid(),
408 'starred' => '1',
409 'archive' => '0',
410 ]);
411
412 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
413
414 $content = json_decode($this->client->getResponse()->getContent(), true);
415
416 $this->assertEquals($entry->getId(), $content['id']);
417 $this->assertEquals($entry->getUrl(), $content['url']);
418 $this->assertEquals('New awesome title', $content['title']);
419 $this->assertGreaterThan($nbTags, count($content['tags']));
420 $this->assertEquals(1, $content['user_id']);
421 }
422
423 public function testPatchEntryWithoutQuotes()
424 {
425 $entry = $this->client->getContainer()
426 ->get('doctrine.orm.entity_manager')
427 ->getRepository('WallabagCoreBundle:Entry')
428 ->findOneByUser(1);
429
430 if (!$entry) {
431 $this->markTestSkipped('No content found in db.');
432 }
433
434 // hydrate the tags relations
435 $nbTags = count($entry->getTags());
436
437 $this->client->request('PATCH', '/api/entries/'.$entry->getId().'.json', [
438 'title' => 'New awesome title',
439 'tags' => 'new tag '.uniqid(),
440 'starred' => 1,
441 'archive' => 0,
442 ]);
443
444 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
445
446 $content = json_decode($this->client->getResponse()->getContent(), true);
447
448 $this->assertEquals($entry->getId(), $content['id']);
449 $this->assertEquals($entry->getUrl(), $content['url']);
450 $this->assertEquals('New awesome title', $content['title']);
451 $this->assertGreaterThan($nbTags, count($content['tags']));
452 }
453
454 public function testGetTagsEntry()
455 {
456 $entry = $this->client->getContainer()
457 ->get('doctrine.orm.entity_manager')
458 ->getRepository('WallabagCoreBundle:Entry')
459 ->findOneWithTags($this->user->getId());
460
461 $entry = $entry[0];
462
463 if (!$entry) {
464 $this->markTestSkipped('No content found in db.');
465 }
466
467 $tags = [];
468 foreach ($entry->getTags() as $tag) {
469 $tags[] = ['id' => $tag->getId(), 'label' => $tag->getLabel(), 'slug' => $tag->getSlug()];
470 }
471
472 $this->client->request('GET', '/api/entries/'.$entry->getId().'/tags');
473
474 $this->assertEquals(json_encode($tags, JSON_HEX_QUOT), $this->client->getResponse()->getContent());
475 }
476
477 public function testPostTagsOnEntry()
478 {
479 $entry = $this->client->getContainer()
480 ->get('doctrine.orm.entity_manager')
481 ->getRepository('WallabagCoreBundle:Entry')
482 ->findOneByUser(1);
483
484 if (!$entry) {
485 $this->markTestSkipped('No content found in db.');
486 }
487
488 $nbTags = count($entry->getTags());
489
490 $newTags = 'tag1,tag2,tag3';
491
492 $this->client->request('POST', '/api/entries/'.$entry->getId().'/tags', ['tags' => $newTags]);
493
494 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
495
496 $content = json_decode($this->client->getResponse()->getContent(), true);
497
498 $this->assertArrayHasKey('tags', $content);
499 $this->assertEquals($nbTags + 3, count($content['tags']));
500
501 $entryDB = $this->client->getContainer()
502 ->get('doctrine.orm.entity_manager')
503 ->getRepository('WallabagCoreBundle:Entry')
504 ->find($entry->getId());
505
506 $tagsInDB = [];
507 foreach ($entryDB->getTags()->toArray() as $tag) {
508 $tagsInDB[$tag->getId()] = $tag->getLabel();
509 }
510
511 foreach (explode(',', $newTags) as $tag) {
512 $this->assertContains($tag, $tagsInDB);
513 }
514 }
515
516 public function testDeleteOneTagEntry()
517 {
518 $entry = $this->client->getContainer()
519 ->get('doctrine.orm.entity_manager')
520 ->getRepository('WallabagCoreBundle:Entry')
521 ->findOneWithTags($this->user->getId());
522 $entry = $entry[0];
523
524 if (!$entry) {
525 $this->markTestSkipped('No content found in db.');
526 }
527
528 // hydrate the tags relations
529 $nbTags = count($entry->getTags());
530 $tag = $entry->getTags()[0];
531
532 $this->client->request('DELETE', '/api/entries/'.$entry->getId().'/tags/'.$tag->getId().'.json');
533
534 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
535
536 $content = json_decode($this->client->getResponse()->getContent(), true);
537
538 $this->assertArrayHasKey('tags', $content);
539 $this->assertEquals($nbTags - 1, count($content['tags']));
540 }
541
542 public function testSaveIsArchivedAfterPost()
543 {
544 $entry = $this->client->getContainer()
545 ->get('doctrine.orm.entity_manager')
546 ->getRepository('WallabagCoreBundle:Entry')
547 ->findOneBy(['user' => 1, 'isArchived' => true]);
548
549 if (!$entry) {
550 $this->markTestSkipped('No content found in db.');
551 }
552
553 $this->client->request('POST', '/api/entries.json', [
554 'url' => $entry->getUrl(),
555 ]);
556
557 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
558
559 $content = json_decode($this->client->getResponse()->getContent(), true);
560
561 $this->assertEquals(true, $content['is_archived']);
562 }
563
564 public function testSaveIsStarredAfterPost()
565 {
566 $entry = $this->client->getContainer()
567 ->get('doctrine.orm.entity_manager')
568 ->getRepository('WallabagCoreBundle:Entry')
569 ->findOneBy(['user' => 1, 'isStarred' => true]);
570
571 if (!$entry) {
572 $this->markTestSkipped('No content found in db.');
573 }
574
575 $this->client->request('POST', '/api/entries.json', [
576 'url' => $entry->getUrl(),
577 ]);
578
579 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
580
581 $content = json_decode($this->client->getResponse()->getContent(), true);
582
583 $this->assertEquals(true, $content['is_starred']);
584 }
585
586 public function testSaveIsArchivedAfterPatch()
587 {
588 $entry = $this->client->getContainer()
589 ->get('doctrine.orm.entity_manager')
590 ->getRepository('WallabagCoreBundle:Entry')
591 ->findOneBy(['user' => 1, 'isArchived' => true]);
592
593 if (!$entry) {
594 $this->markTestSkipped('No content found in db.');
595 }
596
597 $this->client->request('PATCH', '/api/entries/'.$entry->getId().'.json', [
598 'title' => $entry->getTitle().'++',
599 ]);
600
601 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
602
603 $content = json_decode($this->client->getResponse()->getContent(), true);
604
605 $this->assertEquals(true, $content['is_archived']);
606 }
607
608 public function testSaveIsStarredAfterPatch()
609 {
610 $entry = $this->client->getContainer()
611 ->get('doctrine.orm.entity_manager')
612 ->getRepository('WallabagCoreBundle:Entry')
613 ->findOneBy(['user' => 1, 'isStarred' => true]);
614
615 if (!$entry) {
616 $this->markTestSkipped('No content found in db.');
617 }
618 $this->client->request('PATCH', '/api/entries/'.$entry->getId().'.json', [
619 'title' => $entry->getTitle().'++',
620 ]);
621
622 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
623
624 $content = json_decode($this->client->getResponse()->getContent(), true);
625
626 $this->assertEquals(true, $content['is_starred']);
627 }
628
629 public function testGetEntriesExists()
630 {
631 $this->client->request('GET', '/api/entries/exists?url=http://0.0.0.0/entry2');
632
633 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
634
635 $content = json_decode($this->client->getResponse()->getContent(), true);
636
637 $this->assertEquals(true, $content['exists']);
638 }
639
640 public function testGetEntriesExistsWithManyUrls()
641 {
642 $url1 = 'http://0.0.0.0/entry2';
643 $url2 = 'http://0.0.0.0/entry10';
644 $this->client->request('GET', '/api/entries/exists?urls[]='.$url1.'&urls[]='.$url2);
645
646 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
647
648 $content = json_decode($this->client->getResponse()->getContent(), true);
649
650 $this->assertArrayHasKey($url1, $content);
651 $this->assertArrayHasKey($url2, $content);
652 $this->assertEquals(true, $content[$url1]);
653 $this->assertEquals(false, $content[$url2]);
654 }
655
656 public function testGetEntriesExistsWhichDoesNotExists()
657 {
658 $this->client->request('GET', '/api/entries/exists?url=http://google.com/entry2');
659
660 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
661
662 $content = json_decode($this->client->getResponse()->getContent(), true);
663
664 $this->assertEquals(false, $content['exists']);
665 }
666
667 public function testGetEntriesExistsWithNoUrl()
668 {
669 $this->client->request('GET', '/api/entries/exists?url=');
670
671 $this->assertEquals(403, $this->client->getResponse()->getStatusCode());
672 }
673}
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 5dcb3e00..c87e58de 100644
--- a/tests/Wallabag/ApiBundle/Controller/WallabagRestControllerTest.php
+++ b/tests/Wallabag/ApiBundle/Controller/WallabagRestControllerTest.php
@@ -3,697 +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->assertTrue(
36 $this->client->getResponse()->headers->contains(
37 'Content-Type',
38 'application/json'
39 )
40 );
41 }
42
43 public function testGetOneEntryWrongUser()
44 {
45 $entry = $this->client->getContainer()
46 ->get('doctrine.orm.entity_manager')
47 ->getRepository('WallabagCoreBundle:Entry')
48 ->findOneBy(['user' => 2, 'isArchived' => false]);
49
50 if (!$entry) {
51 $this->markTestSkipped('No content found in db.');
52 }
53
54 $this->client->request('GET', '/api/entries/'.$entry->getId().'.json');
55
56 $this->assertEquals(403, $this->client->getResponse()->getStatusCode());
57 }
58
59 public function testGetEntries()
60 {
61 $this->client->request('GET', '/api/entries');
62
63 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
64
65 $content = json_decode($this->client->getResponse()->getContent(), true);
66
67 $this->assertGreaterThanOrEqual(1, count($content));
68 $this->assertNotEmpty($content['_embedded']['items']);
69 $this->assertGreaterThanOrEqual(1, $content['total']);
70 $this->assertEquals(1, $content['page']);
71 $this->assertGreaterThanOrEqual(1, $content['pages']);
72
73 $this->assertTrue(
74 $this->client->getResponse()->headers->contains(
75 'Content-Type',
76 'application/json'
77 )
78 );
79 }
80
81 public function testGetEntriesWithFullOptions()
82 {
83 $this->client->request('GET', '/api/entries', [
84 'archive' => 1,
85 'starred' => 1,
86 'sort' => 'updated',
87 'order' => 'asc',
88 'page' => 1,
89 'perPage' => 2,
90 'tags' => 'foo',
91 'since' => 1443274283,
92 ]);
93
94 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
95
96 $content = json_decode($this->client->getResponse()->getContent(), true);
97
98 $this->assertGreaterThanOrEqual(1, count($content));
99 $this->assertArrayHasKey('items', $content['_embedded']);
100 $this->assertGreaterThanOrEqual(0, $content['total']);
101 $this->assertEquals(1, $content['page']);
102 $this->assertEquals(2, $content['limit']);
103 $this->assertGreaterThanOrEqual(1, $content['pages']);
104
105 $this->assertArrayHasKey('_links', $content);
106 $this->assertArrayHasKey('self', $content['_links']);
107 $this->assertArrayHasKey('first', $content['_links']);
108 $this->assertArrayHasKey('last', $content['_links']);
109
110 foreach (['self', 'first', 'last'] as $link) {
111 $this->assertArrayHasKey('href', $content['_links'][$link]);
112 $this->assertContains('archive=1', $content['_links'][$link]['href']);
113 $this->assertContains('starred=1', $content['_links'][$link]['href']);
114 $this->assertContains('sort=updated', $content['_links'][$link]['href']);
115 $this->assertContains('order=asc', $content['_links'][$link]['href']);
116 $this->assertContains('tags=foo', $content['_links'][$link]['href']);
117 $this->assertContains('since=1443274283', $content['_links'][$link]['href']);
118 }
119
120 $this->assertTrue(
121 $this->client->getResponse()->headers->contains(
122 'Content-Type',
123 'application/json'
124 )
125 );
126 }
127
128 public function testGetStarredEntries()
129 {
130 $this->client->request('GET', '/api/entries', ['starred' => 1, 'sort' => 'updated']);
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->assertNotEmpty($content['_embedded']['items']);
138 $this->assertGreaterThanOrEqual(1, $content['total']);
139 $this->assertEquals(1, $content['page']);
140 $this->assertGreaterThanOrEqual(1, $content['pages']);
141
142 $this->assertArrayHasKey('_links', $content);
143 $this->assertArrayHasKey('self', $content['_links']);
144 $this->assertArrayHasKey('first', $content['_links']);
145 $this->assertArrayHasKey('last', $content['_links']);
146
147 foreach (['self', 'first', 'last'] as $link) {
148 $this->assertArrayHasKey('href', $content['_links'][$link]);
149 $this->assertContains('starred=1', $content['_links'][$link]['href']);
150 $this->assertContains('sort=updated', $content['_links'][$link]['href']);
151 }
152
153 $this->assertTrue(
154 $this->client->getResponse()->headers->contains(
155 'Content-Type',
156 'application/json'
157 )
158 );
159 }
160
161 public function testGetArchiveEntries()
162 {
163 $this->client->request('GET', '/api/entries', ['archive' => 1]);
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('archive=1', $content['_links'][$link]['href']);
183 }
184
185 $this->assertTrue(
186 $this->client->getResponse()->headers->contains(
187 'Content-Type',
188 'application/json'
189 )
190 );
191 }
192
193 public function testGetTaggedEntries()
194 {
195 $this->client->request('GET', '/api/entries', ['tags' => 'foo,bar']);
196
197 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
198
199 $content = json_decode($this->client->getResponse()->getContent(), true);
200
201 $this->assertGreaterThanOrEqual(1, count($content));
202 $this->assertNotEmpty($content['_embedded']['items']);
203 $this->assertGreaterThanOrEqual(1, $content['total']);
204 $this->assertEquals(1, $content['page']);
205 $this->assertGreaterThanOrEqual(1, $content['pages']);
206
207 $this->assertArrayHasKey('_links', $content);
208 $this->assertArrayHasKey('self', $content['_links']);
209 $this->assertArrayHasKey('first', $content['_links']);
210 $this->assertArrayHasKey('last', $content['_links']);
211
212 foreach (['self', 'first', 'last'] as $link) {
213 $this->assertArrayHasKey('href', $content['_links'][$link]);
214 $this->assertContains('tags='.urlencode('foo,bar'), $content['_links'][$link]['href']);
215 }
216
217 $this->assertTrue(
218 $this->client->getResponse()->headers->contains(
219 'Content-Type',
220 'application/json'
221 )
222 );
223 }
224
225 public function testGetDatedEntries()
226 {
227 $this->client->request('GET', '/api/entries', ['since' => 1443274283]);
228
229 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
230
231 $content = json_decode($this->client->getResponse()->getContent(), true);
232
233 $this->assertGreaterThanOrEqual(1, count($content));
234 $this->assertNotEmpty($content['_embedded']['items']);
235 $this->assertGreaterThanOrEqual(1, $content['total']);
236 $this->assertEquals(1, $content['page']);
237 $this->assertGreaterThanOrEqual(1, $content['pages']);
238
239 $this->assertArrayHasKey('_links', $content);
240 $this->assertArrayHasKey('self', $content['_links']);
241 $this->assertArrayHasKey('first', $content['_links']);
242 $this->assertArrayHasKey('last', $content['_links']);
243
244 foreach (['self', 'first', 'last'] as $link) {
245 $this->assertArrayHasKey('href', $content['_links'][$link]);
246 $this->assertContains('since=1443274283', $content['_links'][$link]['href']);
247 }
248
249 $this->assertTrue(
250 $this->client->getResponse()->headers->contains(
251 'Content-Type',
252 'application/json'
253 )
254 );
255 }
256
257 public function testGetDatedSupEntries()
258 {
259 $future = new \DateTime(date('Y-m-d H:i:s'));
260 $this->client->request('GET', '/api/entries', ['since' => $future->getTimestamp() + 1000]);
261
262 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
263
264 $content = json_decode($this->client->getResponse()->getContent(), true);
265
266 $this->assertGreaterThanOrEqual(1, count($content));
267 $this->assertEmpty($content['_embedded']['items']);
268 $this->assertEquals(0, $content['total']);
269 $this->assertEquals(1, $content['page']);
270 $this->assertEquals(1, $content['pages']);
271
272 $this->assertArrayHasKey('_links', $content);
273 $this->assertArrayHasKey('self', $content['_links']);
274 $this->assertArrayHasKey('first', $content['_links']);
275 $this->assertArrayHasKey('last', $content['_links']);
276
277 foreach (['self', 'first', 'last'] as $link) {
278 $this->assertArrayHasKey('href', $content['_links'][$link]);
279 $this->assertContains('since='.($future->getTimestamp() + 1000), $content['_links'][$link]['href']);
280 }
281
282 $this->assertTrue(
283 $this->client->getResponse()->headers->contains(
284 'Content-Type',
285 'application/json'
286 )
287 );
288 }
289
290 public function testDeleteEntry()
291 {
292 $entry = $this->client->getContainer()
293 ->get('doctrine.orm.entity_manager')
294 ->getRepository('WallabagCoreBundle:Entry')
295 ->findOneByUser(1);
296
297 if (!$entry) {
298 $this->markTestSkipped('No content found in db.');
299 }
300
301 $this->client->request('DELETE', '/api/entries/'.$entry->getId().'.json');
302
303 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
304
305 $content = json_decode($this->client->getResponse()->getContent(), true);
306
307 $this->assertEquals($entry->getTitle(), $content['title']);
308 $this->assertEquals($entry->getUrl(), $content['url']);
309
310 // We'll try to delete this entry again
311 $this->client->request('DELETE', '/api/entries/'.$entry->getId().'.json');
312
313 $this->assertEquals(404, $this->client->getResponse()->getStatusCode());
314 }
315
316 public function testPostEntry()
317 {
318 $this->client->request('POST', '/api/entries.json', [
319 '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',
320 'tags' => 'google',
321 'title' => 'New title for my article',
322 ]);
323
324 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
325
326 $content = json_decode($this->client->getResponse()->getContent(), true);
327
328 $this->assertGreaterThan(0, $content['id']);
329 $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']);
330 $this->assertEquals(false, $content['is_archived']);
331 $this->assertEquals(false, $content['is_starred']);
332 $this->assertEquals('New title for my article', $content['title']);
333 $this->assertEquals(1, $content['user_id']);
334 $this->assertCount(1, $content['tags']);
335 }
336
337 public function testPostSameEntry()
338 {
339 $this->client->request('POST', '/api/entries.json', [
340 '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',
341 'archive' => '1',
342 'tags' => 'google, apple',
343 ]);
344
345 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
346
347 $content = json_decode($this->client->getResponse()->getContent(), true);
348
349 $this->assertGreaterThan(0, $content['id']);
350 $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']);
351 $this->assertEquals(true, $content['is_archived']);
352 $this->assertEquals(false, $content['is_starred']);
353 $this->assertCount(2, $content['tags']);
354 }
355
356 public function testPostArchivedAndStarredEntry()
357 {
358 $this->client->request('POST', '/api/entries.json', [
359 'url' => 'http://www.lemonde.fr/idees/article/2016/02/08/preserver-la-liberte-d-expression-sur-les-reseaux-sociaux_4861503_3232.html',
360 'archive' => '1',
361 'starred' => '1',
362 ]);
363
364 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
365
366 $content = json_decode($this->client->getResponse()->getContent(), true);
367
368 $this->assertGreaterThan(0, $content['id']);
369 $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']);
370 $this->assertEquals(true, $content['is_archived']);
371 $this->assertEquals(true, $content['is_starred']);
372 $this->assertEquals(1, $content['user_id']);
373 }
374
375 public function testPostArchivedAndStarredEntryWithoutQuotes()
376 {
377 $this->client->request('POST', '/api/entries.json', [
378 'url' => 'http://www.lemonde.fr/idees/article/2016/02/08/preserver-la-liberte-d-expression-sur-les-reseaux-sociaux_4861503_3232.html',
379 'archive' => 0,
380 'starred' => 1,
381 ]);
382
383 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
384
385 $content = json_decode($this->client->getResponse()->getContent(), true);
386
387 $this->assertGreaterThan(0, $content['id']);
388 $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']);
389 $this->assertEquals(false, $content['is_archived']);
390 $this->assertEquals(true, $content['is_starred']);
391 }
392
393 public function testPatchEntry()
394 {
395 $entry = $this->client->getContainer()
396 ->get('doctrine.orm.entity_manager')
397 ->getRepository('WallabagCoreBundle:Entry')
398 ->findOneByUser(1);
399
400 if (!$entry) {
401 $this->markTestSkipped('No content found in db.');
402 }
403
404 // hydrate the tags relations
405 $nbTags = count($entry->getTags());
406
407 $this->client->request('PATCH', '/api/entries/'.$entry->getId().'.json', [
408 'title' => 'New awesome title',
409 'tags' => 'new tag '.uniqid(),
410 'starred' => '1',
411 'archive' => '0',
412 ]);
413
414 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
415
416 $content = json_decode($this->client->getResponse()->getContent(), true);
417
418 $this->assertEquals($entry->getId(), $content['id']);
419 $this->assertEquals($entry->getUrl(), $content['url']);
420 $this->assertEquals('New awesome title', $content['title']);
421 $this->assertGreaterThan($nbTags, count($content['tags']));
422 $this->assertEquals(1, $content['user_id']);
423 }
424
425 public function testPatchEntryWithoutQuotes()
426 {
427 $entry = $this->client->getContainer()
428 ->get('doctrine.orm.entity_manager')
429 ->getRepository('WallabagCoreBundle:Entry')
430 ->findOneByUser(1);
431
432 if (!$entry) {
433 $this->markTestSkipped('No content found in db.');
434 }
435
436 // hydrate the tags relations
437 $nbTags = count($entry->getTags());
438
439 $this->client->request('PATCH', '/api/entries/'.$entry->getId().'.json', [
440 'title' => 'New awesome title',
441 'tags' => 'new tag '.uniqid(),
442 'starred' => 1,
443 'archive' => 0,
444 ]);
445
446 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
447
448 $content = json_decode($this->client->getResponse()->getContent(), true);
449
450 $this->assertEquals($entry->getId(), $content['id']);
451 $this->assertEquals($entry->getUrl(), $content['url']);
452 $this->assertEquals('New awesome title', $content['title']);
453 $this->assertGreaterThan($nbTags, count($content['tags']));
454 }
455
456 public function testGetTagsEntry()
457 {
458 $entry = $this->client->getContainer()
459 ->get('doctrine.orm.entity_manager')
460 ->getRepository('WallabagCoreBundle:Entry')
461 ->findOneWithTags($this->user->getId());
462
463 $entry = $entry[0];
464
465 if (!$entry) {
466 $this->markTestSkipped('No content found in db.');
467 }
468
469 $tags = [];
470 foreach ($entry->getTags() as $tag) {
471 $tags[] = ['id' => $tag->getId(), 'label' => $tag->getLabel(), 'slug' => $tag->getSlug()];
472 }
473
474 $this->client->request('GET', '/api/entries/'.$entry->getId().'/tags');
475
476 $this->assertEquals(json_encode($tags, JSON_HEX_QUOT), $this->client->getResponse()->getContent());
477 }
478
479 public function testPostTagsOnEntry()
480 {
481 $entry = $this->client->getContainer()
482 ->get('doctrine.orm.entity_manager')
483 ->getRepository('WallabagCoreBundle:Entry')
484 ->findOneByUser(1);
485
486 if (!$entry) {
487 $this->markTestSkipped('No content found in db.');
488 }
489
490 $nbTags = count($entry->getTags());
491
492 $newTags = 'tag1,tag2,tag3';
493
494 $this->client->request('POST', '/api/entries/'.$entry->getId().'/tags', ['tags' => $newTags]);
495
496 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
497
498 $content = json_decode($this->client->getResponse()->getContent(), true);
499
500 $this->assertArrayHasKey('tags', $content);
501 $this->assertEquals($nbTags + 3, count($content['tags']));
502
503 $entryDB = $this->client->getContainer()
504 ->get('doctrine.orm.entity_manager')
505 ->getRepository('WallabagCoreBundle:Entry')
506 ->find($entry->getId());
507
508 $tagsInDB = [];
509 foreach ($entryDB->getTags()->toArray() as $tag) {
510 $tagsInDB[$tag->getId()] = $tag->getLabel();
511 }
512
513 foreach (explode(',', $newTags) as $tag) {
514 $this->assertContains($tag, $tagsInDB);
515 }
516 }
517
518 public function testDeleteOneTagEntry()
519 {
520 $entry = $this->client->getContainer()
521 ->get('doctrine.orm.entity_manager')
522 ->getRepository('WallabagCoreBundle:Entry')
523 ->findOneWithTags($this->user->getId());
524 $entry = $entry[0];
525
526 if (!$entry) {
527 $this->markTestSkipped('No content found in db.');
528 }
529
530 // hydrate the tags relations
531 $nbTags = count($entry->getTags());
532 $tag = $entry->getTags()[0];
533
534 $this->client->request('DELETE', '/api/entries/'.$entry->getId().'/tags/'.$tag->getId().'.json');
535
536 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
537
538 $content = json_decode($this->client->getResponse()->getContent(), true);
539
540 $this->assertArrayHasKey('tags', $content);
541 $this->assertEquals($nbTags - 1, count($content['tags']));
542 }
543
544 public function testGetUserTags()
545 {
546 $this->client->request('GET', '/api/tags.json');
547
548 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
549
550 $content = json_decode($this->client->getResponse()->getContent(), true);
551
552 $this->assertGreaterThan(0, $content);
553 $this->assertArrayHasKey('id', $content[0]);
554 $this->assertArrayHasKey('label', $content[0]);
555
556 return end($content);
557 }
558
559 /**
560 * @depends testGetUserTags
561 */
562 public function testDeleteUserTag($tag)
563 {
564 $tagName = $tag['label'];
565
566 $this->client->request('DELETE', '/api/tags/'.$tag['id'].'.json');
567
568 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
569
570 $content = json_decode($this->client->getResponse()->getContent(), true);
571
572 $this->assertArrayHasKey('label', $content);
573 $this->assertEquals($tag['label'], $content['label']);
574 $this->assertEquals($tag['slug'], $content['slug']);
575
576 $entries = $this->client->getContainer()
577 ->get('doctrine.orm.entity_manager')
578 ->getRepository('WallabagCoreBundle:Entry')
579 ->findAllByTagId($this->user->getId(), $tag['id']);
580
581 $this->assertCount(0, $entries);
582
583 $tag = $this->client->getContainer()
584 ->get('doctrine.orm.entity_manager')
585 ->getRepository('WallabagCoreBundle:Tag')
586 ->findOneByLabel($tagName);
587
588 $this->assertNull($tag, $tagName.' was removed because it begun an orphan tag');
589 }
590
591 public function testDeleteTagByLabel()
592 {
593 $em = $this->client->getContainer()->get('doctrine.orm.entity_manager');
594 $entry = $this->client->getContainer()
595 ->get('doctrine.orm.entity_manager')
596 ->getRepository('WallabagCoreBundle:Entry')
597 ->findOneWithTags($this->user->getId());
598
599 $entry = $entry[0];
600
601 $tag = new Tag();
602 $tag->setLabel('Awesome tag for test');
603 $em->persist($tag);
604
605 $entry->addTag($tag);
606
607 $em->persist($entry);
608 $em->flush();
609
610 $this->client->request('DELETE', '/api/tag/label.json', ['tag' => $tag->getLabel()]);
611
612 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
613
614 $content = json_decode($this->client->getResponse()->getContent(), true);
615
616 $this->assertArrayHasKey('label', $content);
617 $this->assertEquals($tag->getLabel(), $content['label']);
618 $this->assertEquals($tag->getSlug(), $content['slug']);
619
620 $entries = $this->client->getContainer()
621 ->get('doctrine.orm.entity_manager')
622 ->getRepository('WallabagCoreBundle:Entry')
623 ->findAllByTagId($this->user->getId(), $tag->getId());
624
625 $this->assertCount(0, $entries);
626 }
627
628 public function testDeleteTagByLabelNotFound()
629 {
630 $this->client->request('DELETE', '/api/tag/label.json', ['tag' => 'does not exist']);
631
632 $this->assertEquals(404, $this->client->getResponse()->getStatusCode());
633 }
634
635 public function testDeleteTagsByLabel()
636 {
637 $em = $this->client->getContainer()->get('doctrine.orm.entity_manager');
638 $entry = $this->client->getContainer()
639 ->get('doctrine.orm.entity_manager')
640 ->getRepository('WallabagCoreBundle:Entry')
641 ->findOneWithTags($this->user->getId());
642
643 $entry = $entry[0];
644
645 $tag = new Tag();
646 $tag->setLabel('Awesome tag for tagsLabel');
647 $em->persist($tag);
648
649 $tag2 = new Tag();
650 $tag2->setLabel('Awesome tag for tagsLabel 2');
651 $em->persist($tag2);
652
653 $entry->addTag($tag);
654 $entry->addTag($tag2);
655
656 $em->persist($entry);
657 $em->flush();
658
659 $this->client->request('DELETE', '/api/tags/label.json', ['tags' => $tag->getLabel().','.$tag2->getLabel()]);
660
661 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
662
663 $content = json_decode($this->client->getResponse()->getContent(), true);
664
665 $this->assertCount(2, $content);
666
667 $this->assertArrayHasKey('label', $content[0]);
668 $this->assertEquals($tag->getLabel(), $content[0]['label']);
669 $this->assertEquals($tag->getSlug(), $content[0]['slug']);
670
671 $this->assertArrayHasKey('label', $content[1]);
672 $this->assertEquals($tag2->getLabel(), $content[1]['label']);
673 $this->assertEquals($tag2->getSlug(), $content[1]['slug']);
674
675 $entries = $this->client->getContainer()
676 ->get('doctrine.orm.entity_manager')
677 ->getRepository('WallabagCoreBundle:Entry')
678 ->findAllByTagId($this->user->getId(), $tag->getId());
679
680 $this->assertCount(0, $entries);
681
682 $entries = $this->client->getContainer()
683 ->get('doctrine.orm.entity_manager')
684 ->getRepository('WallabagCoreBundle:Entry')
685 ->findAllByTagId($this->user->getId(), $tag2->getId());
686
687 $this->assertCount(0, $entries);
688 }
689
690 public function testDeleteTagsByLabelNotFound()
691 {
692 $this->client->request('DELETE', '/api/tags/label.json', ['tags' => 'does not exist']);
693
694 $this->assertEquals(404, $this->client->getResponse()->getStatusCode());
695 }
696
697 public function testGetVersion() 9 public function testGetVersion()
698 { 10 {
699 $this->client->request('GET', '/api/version'); 11 $this->client->request('GET', '/api/version');
@@ -704,136 +16,4 @@ class WallabagRestControllerTest extends WallabagApiTestCase
704 16
705 $this->assertEquals($this->client->getContainer()->getParameter('wallabag_core.version'), $content); 17 $this->assertEquals($this->client->getContainer()->getParameter('wallabag_core.version'), $content);
706 } 18 }
707
708 public function testSaveIsArchivedAfterPost()
709 {
710 $entry = $this->client->getContainer()
711 ->get('doctrine.orm.entity_manager')
712 ->getRepository('WallabagCoreBundle:Entry')
713 ->findOneBy(['user' => 1, 'isArchived' => true]);
714
715 if (!$entry) {
716 $this->markTestSkipped('No content found in db.');
717 }
718
719 $this->client->request('POST', '/api/entries.json', [
720 'url' => $entry->getUrl(),
721 ]);
722
723 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
724
725 $content = json_decode($this->client->getResponse()->getContent(), true);
726
727 $this->assertEquals(true, $content['is_archived']);
728 }
729
730 public function testSaveIsStarredAfterPost()
731 {
732 $entry = $this->client->getContainer()
733 ->get('doctrine.orm.entity_manager')
734 ->getRepository('WallabagCoreBundle:Entry')
735 ->findOneBy(['user' => 1, 'isStarred' => true]);
736
737 if (!$entry) {
738 $this->markTestSkipped('No content found in db.');
739 }
740
741 $this->client->request('POST', '/api/entries.json', [
742 'url' => $entry->getUrl(),
743 ]);
744
745 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
746
747 $content = json_decode($this->client->getResponse()->getContent(), true);
748
749 $this->assertEquals(true, $content['is_starred']);
750 }
751
752 public function testSaveIsArchivedAfterPatch()
753 {
754 $entry = $this->client->getContainer()
755 ->get('doctrine.orm.entity_manager')
756 ->getRepository('WallabagCoreBundle:Entry')
757 ->findOneBy(['user' => 1, 'isArchived' => true]);
758
759 if (!$entry) {
760 $this->markTestSkipped('No content found in db.');
761 }
762
763 $this->client->request('PATCH', '/api/entries/'.$entry->getId().'.json', [
764 'title' => $entry->getTitle().'++',
765 ]);
766
767 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
768
769 $content = json_decode($this->client->getResponse()->getContent(), true);
770
771 $this->assertEquals(true, $content['is_archived']);
772 }
773
774 public function testSaveIsStarredAfterPatch()
775 {
776 $entry = $this->client->getContainer()
777 ->get('doctrine.orm.entity_manager')
778 ->getRepository('WallabagCoreBundle:Entry')
779 ->findOneBy(['user' => 1, 'isStarred' => true]);
780
781 if (!$entry) {
782 $this->markTestSkipped('No content found in db.');
783 }
784 $this->client->request('PATCH', '/api/entries/'.$entry->getId().'.json', [
785 'title' => $entry->getTitle().'++',
786 ]);
787
788 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
789
790 $content = json_decode($this->client->getResponse()->getContent(), true);
791
792 $this->assertEquals(true, $content['is_starred']);
793 }
794
795 public function testGetEntriesExists()
796 {
797 $this->client->request('GET', '/api/entries/exists?url=http://0.0.0.0/entry2');
798
799 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
800
801 $content = json_decode($this->client->getResponse()->getContent(), true);
802
803 $this->assertEquals(true, $content['exists']);
804 }
805
806 public function testGetEntriesExistsWithManyUrls()
807 {
808 $url1 = 'http://0.0.0.0/entry2';
809 $url2 = 'http://0.0.0.0/entry10';
810 $this->client->request('GET', '/api/entries/exists?urls[]='.$url1.'&urls[]='.$url2);
811
812 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
813
814 $content = json_decode($this->client->getResponse()->getContent(), true);
815
816 $this->assertArrayHasKey($url1, $content);
817 $this->assertArrayHasKey($url2, $content);
818 $this->assertEquals(true, $content[$url1]);
819 $this->assertEquals(false, $content[$url2]);
820 }
821
822 public function testGetEntriesExistsWhichDoesNotExists()
823 {
824 $this->client->request('GET', '/api/entries/exists?url=http://google.com/entry2');
825
826 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
827
828 $content = json_decode($this->client->getResponse()->getContent(), true);
829
830 $this->assertEquals(false, $content['exists']);
831 }
832
833 public function testGetEntriesExistsWithNoUrl()
834 {
835 $this->client->request('GET', '/api/entries/exists?url=');
836
837 $this->assertEquals(403, $this->client->getResponse()->getStatusCode());
838 }
839} 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);