aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--app/DoctrineMigrations/Version20160812120952.php2
-rw-r--r--app/DoctrineMigrations/Version20161001072726.php63
-rw-r--r--app/DoctrineMigrations/Version20161022134138.php77
-rw-r--r--app/config/config.yml31
-rw-r--r--app/config/config_test.yml2
-rw-r--r--app/config/parameters.yml.dist14
-rw-r--r--app/config/parameters_test.yml1
-rw-r--r--app/config/routing_rest.yml5
-rw-r--r--app/config/tests/parameters_test.mysql.yml1
-rw-r--r--app/config/tests/parameters_test.pgsql.yml1
-rw-r--r--app/config/tests/parameters_test.sqlite.yml1
-rw-r--r--composer.json6
-rw-r--r--docs/en/user/configuration.rst2
-rw-r--r--docs/en/user/parameters.rst1
-rw-r--r--docs/fr/user/configuration.rst2
-rw-r--r--src/Wallabag/AnnotationBundle/Controller/WallabagAnnotationController.php74
-rw-r--r--src/Wallabag/AnnotationBundle/Entity/Annotation.php2
-rw-r--r--src/Wallabag/AnnotationBundle/Repository/AnnotationRepository.php20
-rw-r--r--src/Wallabag/ApiBundle/Controller/WallabagRestController.php126
-rw-r--r--src/Wallabag/ApiBundle/Resources/config/routing_rest.yml8
-rw-r--r--src/Wallabag/CoreBundle/Command/InstallCommand.php30
-rw-r--r--src/Wallabag/CoreBundle/Controller/ConfigController.php111
-rw-r--r--src/Wallabag/CoreBundle/Controller/TagController.php10
-rw-r--r--src/Wallabag/CoreBundle/Entity/Entry.php6
-rw-r--r--src/Wallabag/CoreBundle/Repository/EntryRepository.php14
-rw-r--r--src/Wallabag/CoreBundle/Repository/TagRepository.php18
-rw-r--r--src/Wallabag/CoreBundle/Resources/config/services.yml7
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.da.yml15
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.de.yml15
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.en.yml15
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.es.yml15
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml15
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml19
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.it.yml15
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml93
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml15
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml15
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml15
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/baggy/Config/index.html.twig31
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/material/Config/index.html.twig28
-rw-r--r--src/Wallabag/CoreBundle/Subscriber/SQLiteCascadeDeleteSubscriber.php70
-rw-r--r--src/Wallabag/UserBundle/Repository/UserRepository.php14
-rw-r--r--src/Wallabag/UserBundle/Resources/config/services.yml2
-rw-r--r--tests/Wallabag/AnnotationBundle/Controller/AnnotationControllerTest.php150
-rw-r--r--tests/Wallabag/AnnotationBundle/WallabagAnnotationTestCase.php6
-rw-r--r--tests/Wallabag/ApiBundle/Controller/WallabagRestControllerTest.php104
-rw-r--r--tests/Wallabag/CoreBundle/Controller/ConfigControllerTest.php265
47 files changed, 1331 insertions, 221 deletions
diff --git a/app/DoctrineMigrations/Version20160812120952.php b/app/DoctrineMigrations/Version20160812120952.php
index 3aafea64..39423e2f 100644
--- a/app/DoctrineMigrations/Version20160812120952.php
+++ b/app/DoctrineMigrations/Version20160812120952.php
@@ -33,9 +33,11 @@ class Version20160812120952 extends AbstractMigration implements ContainerAwareI
33 case 'sqlite': 33 case 'sqlite':
34 $this->addSql('ALTER TABLE '.$this->getTable('oauth2_clients').' ADD name longtext DEFAULT NULL'); 34 $this->addSql('ALTER TABLE '.$this->getTable('oauth2_clients').' ADD name longtext DEFAULT NULL');
35 break; 35 break;
36
36 case 'mysql': 37 case 'mysql':
37 $this->addSql('ALTER TABLE '.$this->getTable('oauth2_clients').' ADD name longtext COLLATE \'utf8_unicode_ci\' DEFAULT NULL'); 38 $this->addSql('ALTER TABLE '.$this->getTable('oauth2_clients').' ADD name longtext COLLATE \'utf8_unicode_ci\' DEFAULT NULL');
38 break; 39 break;
40
39 case 'postgresql': 41 case 'postgresql':
40 $this->addSql('ALTER TABLE '.$this->getTable('oauth2_clients').' ADD name text DEFAULT NULL'); 42 $this->addSql('ALTER TABLE '.$this->getTable('oauth2_clients').' ADD name text DEFAULT NULL');
41 } 43 }
diff --git a/app/DoctrineMigrations/Version20161001072726.php b/app/DoctrineMigrations/Version20161001072726.php
new file mode 100644
index 00000000..237db932
--- /dev/null
+++ b/app/DoctrineMigrations/Version20161001072726.php
@@ -0,0 +1,63 @@
1<?php
2
3namespace Application\Migrations;
4
5use Doctrine\DBAL\Migrations\AbstractMigration;
6use Doctrine\DBAL\Schema\Schema;
7use Symfony\Component\DependencyInjection\ContainerAwareInterface;
8use Symfony\Component\DependencyInjection\ContainerInterface;
9
10class Version20161001072726 extends AbstractMigration implements ContainerAwareInterface
11{
12 /**
13 * @var ContainerInterface
14 */
15 private $container;
16
17 public function setContainer(ContainerInterface $container = null)
18 {
19 $this->container = $container;
20 }
21
22 private function getTable($tableName)
23 {
24 return $this->container->getParameter('database_table_prefix') . $tableName;
25 }
26
27 /**
28 * @param Schema $schema
29 */
30 public function up(Schema $schema)
31 {
32 $this->skipIf($this->connection->getDatabasePlatform()->getName() == 'sqlite', 'Migration can only be executed safely on \'mysql\' or \'postgresql\'.');
33
34 // remove all FK from entry_tag
35 $query = $this->connection->query("SELECT CONSTRAINT_NAME FROM information_schema.key_column_usage WHERE TABLE_NAME = '".$this->getTable('entry_tag')."' AND CONSTRAINT_NAME LIKE 'FK_%' AND TABLE_SCHEMA = '".$this->connection->getDatabase()."'");
36 $query->execute();
37
38 foreach ($query->fetchAll() as $fk) {
39 $this->addSql('ALTER TABLE '.$this->getTable('entry_tag').' DROP FOREIGN KEY '.$fk['CONSTRAINT_NAME']);
40 }
41
42 $this->addSql('ALTER TABLE '.$this->getTable('entry_tag').' ADD CONSTRAINT FK_entry_tag_entry FOREIGN KEY (entry_id) REFERENCES '.$this->getTable('entry').' (id) ON DELETE CASCADE');
43 $this->addSql('ALTER TABLE '.$this->getTable('entry_tag').' ADD CONSTRAINT FK_entry_tag_tag FOREIGN KEY (tag_id) REFERENCES '.$this->getTable('tag').' (id) ON DELETE CASCADE');
44
45 // remove entry FK from annotation
46 $query = $this->connection->query("SELECT CONSTRAINT_NAME FROM information_schema.key_column_usage WHERE TABLE_NAME = '".$this->getTable('annotation')."' AND CONSTRAINT_NAME LIKE 'FK_%' and COLUMN_NAME = 'entry_id' AND TABLE_SCHEMA = '".$this->connection->getDatabase()."'");
47 $query->execute();
48
49 foreach ($query->fetchAll() as $fk) {
50 $this->addSql('ALTER TABLE '.$this->getTable('annotation').' DROP FOREIGN KEY '.$fk['CONSTRAINT_NAME']);
51 }
52
53 $this->addSql('ALTER TABLE '.$this->getTable('annotation').' ADD CONSTRAINT FK_annotation_entry FOREIGN KEY (entry_id) REFERENCES '.$this->getTable('entry').' (id) ON DELETE CASCADE');
54 }
55
56 /**
57 * @param Schema $schema
58 */
59 public function down(Schema $schema)
60 {
61 throw new SkipMigrationException('Too complex ...');
62 }
63}
diff --git a/app/DoctrineMigrations/Version20161022134138.php b/app/DoctrineMigrations/Version20161022134138.php
new file mode 100644
index 00000000..5cce55a5
--- /dev/null
+++ b/app/DoctrineMigrations/Version20161022134138.php
@@ -0,0 +1,77 @@
1<?php
2
3namespace Application\Migrations;
4
5use Doctrine\DBAL\Migrations\AbstractMigration;
6use Doctrine\DBAL\Schema\Schema;
7use Symfony\Component\DependencyInjection\ContainerAwareInterface;
8use Symfony\Component\DependencyInjection\ContainerInterface;
9
10class Version20161022134138 extends AbstractMigration implements ContainerAwareInterface
11{
12 /**
13 * @var ContainerInterface
14 */
15 private $container;
16
17 public function setContainer(ContainerInterface $container = null)
18 {
19 $this->container = $container;
20 }
21
22 private function getTable($tableName)
23 {
24 return $this->container->getParameter('database_table_prefix') . $tableName;
25 }
26
27 /**
28 * @param Schema $schema
29 */
30 public function up(Schema $schema)
31 {
32 $this->skipIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'This migration only apply to MySQL');
33
34 $this->addSql('ALTER DATABASE '.$this->container->getParameter('database_name').' CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;');
35
36 $this->addSql('ALTER TABLE '.$this->getTable('annotation').' CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;');
37 $this->addSql('ALTER TABLE '.$this->getTable('entry').' CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;');
38 $this->addSql('ALTER TABLE '.$this->getTable('tag').' CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;');
39 $this->addSql('ALTER TABLE '.$this->getTable('user').' CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;');
40
41 $this->addSql('ALTER TABLE '.$this->getTable('annotation').' CHANGE `text` `text` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;');
42 $this->addSql('ALTER TABLE '.$this->getTable('annotation').' CHANGE `quote` `quote` VARCHAR(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;');
43
44 $this->addSql('ALTER TABLE '.$this->getTable('entry').' CHANGE `title` `title` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;');
45 $this->addSql('ALTER TABLE '.$this->getTable('entry').' CHANGE `content` `content` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;');
46
47 $this->addSql('ALTER TABLE '.$this->getTable('tag').' CHANGE `label` `label` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;');
48
49 $this->addSql('ALTER TABLE '.$this->getTable('user').' CHANGE `name` `name` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;');
50 }
51
52 /**
53 * @param Schema $schema
54 */
55 public function down(Schema $schema)
56 {
57 $this->skipIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'This migration only apply to MySQL');
58
59 $this->addSql('ALTER DATABASE '.$this->container->getParameter('database_name').' CHARACTER SET = utf8 COLLATE = utf8_unicode_ci;');
60
61 $this->addSql('ALTER TABLE '.$this->getTable('annotation').' CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;');
62 $this->addSql('ALTER TABLE '.$this->getTable('entry').' CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;');
63 $this->addSql('ALTER TABLE '.$this->getTable('tag').' CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;');
64 $this->addSql('ALTER TABLE '.$this->getTable('user').' CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;');
65
66 $this->addSql('ALTER TABLE '.$this->getTable('annotation').' CHANGE `text` `text` longtext CHARACTER SET utf8 COLLATE utf8_unicode_ci;');
67 $this->addSql('ALTER TABLE '.$this->getTable('annotation').' CHANGE `quote` `quote` VARCHAR(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci;');
68
69 $this->addSql('ALTER TABLE '.$this->getTable('entry').' CHANGE `title` `title` longtext CHARACTER SET utf8 COLLATE utf8_unicode_ci;');
70 $this->addSql('ALTER TABLE '.$this->getTable('entry').' CHANGE `content` `content` longtext CHARACTER SET utf8 COLLATE utf8_unicode_ci;');
71
72 $this->addSql('ALTER TABLE '.$this->getTable('tag').' CHANGE `label` `label` longtext CHARACTER SET utf8 COLLATE utf8_unicode_ci;');
73
74 $this->addSql('ALTER TABLE '.$this->getTable('user').' CHANGE `name` `name` longtext CHARACTER SET utf8 COLLATE utf8_unicode_ci;');
75
76 }
77}
diff --git a/app/config/config.yml b/app/config/config.yml
index a56cbdd9..dfb0e3b2 100644
--- a/app/config/config.yml
+++ b/app/config/config.yml
@@ -76,7 +76,7 @@ doctrine:
76 dbname: "%database_name%" 76 dbname: "%database_name%"
77 user: "%database_user%" 77 user: "%database_user%"
78 password: "%database_password%" 78 password: "%database_password%"
79 charset: UTF8 79 charset: "%database_charset%"
80 path: "%database_path%" 80 path: "%database_path%"
81 unix_socket: "%database_socket%" 81 unix_socket: "%database_socket%"
82 server_version: 5.6 82 server_version: 5.6
@@ -113,12 +113,26 @@ swiftmailer:
113fos_rest: 113fos_rest:
114 param_fetcher_listener: true 114 param_fetcher_listener: true
115 body_listener: true 115 body_listener: true
116 format_listener: true
117 view: 116 view:
117 mime_types:
118 csv:
119 - 'text/csv'
120 - 'text/plain'
121 pdf:
122 - 'application/pdf'
123 epub:
124 - 'application/epub+zip'
125 mobi:
126 - 'application/x-mobipocket-ebook'
118 view_response_listener: 'force' 127 view_response_listener: 'force'
119 formats: 128 formats:
120 xml: true 129 xml: true
121 json : true 130 json: true
131 txt: true
132 csv: true
133 pdf: true
134 epub: true
135 mobi: true
122 templating_formats: 136 templating_formats:
123 html: true 137 html: true
124 force_redirects: 138 force_redirects:
@@ -127,10 +141,21 @@ fos_rest:
127 default_engine: twig 141 default_engine: twig
128 routing_loader: 142 routing_loader:
129 default_format: json 143 default_format: json
144 format_listener:
145 enabled: true
146 rules:
147 - { path: "^/api/entries/([0-9]+)/export.(.*)", priorities: ['epub', 'mobi', 'pdf', 'txt', 'csv'], fallback_format: false, prefer_extension: false }
148 - { path: "^/api", priorities: ['json', 'xml'], fallback_format: false, prefer_extension: false }
149 - { path: "^/annotations", priorities: ['json', 'xml'], fallback_format: false, prefer_extension: false }
150 # for an unknown reason, EACH REQUEST goes to FOS\RestBundle\EventListener\FormatListener
151 # so we need to add custom rule for custom api export but also for all other routes of the application...
152 - { path: '^/', priorities: ['text/html', '*/*'], fallback_format: html, prefer_extension: false }
130 153
131nelmio_api_doc: 154nelmio_api_doc:
132 sandbox: 155 sandbox:
133 enabled: false 156 enabled: false
157 cache:
158 enabled: true
134 name: wallabag API documentation 159 name: wallabag API documentation
135 160
136nelmio_cors: 161nelmio_cors:
diff --git a/app/config/config_test.yml b/app/config/config_test.yml
index 3eab6fb2..f5e2c25e 100644
--- a/app/config/config_test.yml
+++ b/app/config/config_test.yml
@@ -28,7 +28,7 @@ doctrine:
28 dbname: "%test_database_name%" 28 dbname: "%test_database_name%"
29 user: "%test_database_user%" 29 user: "%test_database_user%"
30 password: "%test_database_password%" 30 password: "%test_database_password%"
31 charset: UTF8 31 charset: "%test_database_charset%"
32 path: "%test_database_path%" 32 path: "%test_database_path%"
33 orm: 33 orm:
34 metadata_cache_driver: 34 metadata_cache_driver:
diff --git a/app/config/parameters.yml.dist b/app/config/parameters.yml.dist
index ece4903a..7a22cb98 100644
--- a/app/config/parameters.yml.dist
+++ b/app/config/parameters.yml.dist
@@ -19,16 +19,18 @@ parameters:
19 database_path: "%kernel.root_dir%/../data/db/wallabag.sqlite" 19 database_path: "%kernel.root_dir%/../data/db/wallabag.sqlite"
20 database_table_prefix: wallabag_ 20 database_table_prefix: wallabag_
21 database_socket: null 21 database_socket: null
22 # with MySQL, use "utf8mb4" if you got problem with content with emojis
23 database_charset: utf8
22 24
23 mailer_transport: smtp 25 mailer_transport: smtp
24 mailer_host: 127.0.0.1 26 mailer_host: 127.0.0.1
25 mailer_user: ~ 27 mailer_user: ~
26 mailer_password: ~ 28 mailer_password: ~
27 29
28 locale: en 30 locale: en
29 31
30 # A secret key that's used to generate certain security-related tokens 32 # A secret key that's used to generate certain security-related tokens
31 secret: ovmpmAWXRCabNlMgzlzFXDYmCFfzGv 33 secret: ovmpmAWXRCabNlMgzlzFXDYmCFfzGv
32 34
33 # two factor stuff 35 # two factor stuff
34 twofactor_auth: true 36 twofactor_auth: true
diff --git a/app/config/parameters_test.yml b/app/config/parameters_test.yml
index 2943b27a..5f2e25bb 100644
--- a/app/config/parameters_test.yml
+++ b/app/config/parameters_test.yml
@@ -6,3 +6,4 @@ parameters:
6 test_database_user: null 6 test_database_user: null
7 test_database_password: null 7 test_database_password: null
8 test_database_path: '%kernel.root_dir%/../data/db/wallabag_test.sqlite' 8 test_database_path: '%kernel.root_dir%/../data/db/wallabag_test.sqlite'
9 test_database_charset: utf8
diff --git a/app/config/routing_rest.yml b/app/config/routing_rest.yml
index 52d395dd..29f4ab14 100644
--- a/app/config/routing_rest.yml
+++ b/app/config/routing_rest.yml
@@ -1,4 +1,3 @@
1Rest_Wallabag: 1Rest_Wallabag:
2 type : rest 2 type : rest
3 resource: "@WallabagApiBundle/Resources/config/routing_rest.yml" 3 resource: "@WallabagApiBundle/Resources/config/routing_rest.yml"
4
diff --git a/app/config/tests/parameters_test.mysql.yml b/app/config/tests/parameters_test.mysql.yml
index d8512845..bca2d466 100644
--- a/app/config/tests/parameters_test.mysql.yml
+++ b/app/config/tests/parameters_test.mysql.yml
@@ -6,3 +6,4 @@ parameters:
6 test_database_user: root 6 test_database_user: root
7 test_database_password: ~ 7 test_database_password: ~
8 test_database_path: ~ 8 test_database_path: ~
9 test_database_charset: utf8mb4
diff --git a/app/config/tests/parameters_test.pgsql.yml b/app/config/tests/parameters_test.pgsql.yml
index 41383868..3e18d4a0 100644
--- a/app/config/tests/parameters_test.pgsql.yml
+++ b/app/config/tests/parameters_test.pgsql.yml
@@ -6,3 +6,4 @@ parameters:
6 test_database_user: travis 6 test_database_user: travis
7 test_database_password: ~ 7 test_database_password: ~
8 test_database_path: ~ 8 test_database_path: ~
9 test_database_charset: utf8
diff --git a/app/config/tests/parameters_test.sqlite.yml b/app/config/tests/parameters_test.sqlite.yml
index 1952e3a6..b8a5f41a 100644
--- a/app/config/tests/parameters_test.sqlite.yml
+++ b/app/config/tests/parameters_test.sqlite.yml
@@ -6,3 +6,4 @@ parameters:
6 test_database_user: ~ 6 test_database_user: ~
7 test_database_password: ~ 7 test_database_password: ~
8 test_database_path: "%kernel.root_dir%/../data/db/wallabag_test.sqlite" 8 test_database_path: "%kernel.root_dir%/../data/db/wallabag_test.sqlite"
9 test_database_charset: utf8
diff --git a/composer.json b/composer.json
index 79de337b..ebc0a7dc 100644
--- a/composer.json
+++ b/composer.json
@@ -54,11 +54,11 @@
54 "sensio/framework-extra-bundle": "^3.0.2", 54 "sensio/framework-extra-bundle": "^3.0.2",
55 "incenteev/composer-parameter-handler": "^2.0", 55 "incenteev/composer-parameter-handler": "^2.0",
56 "nelmio/cors-bundle": "~1.4.0", 56 "nelmio/cors-bundle": "~1.4.0",
57 "friendsofsymfony/rest-bundle": "~1.4", 57 "friendsofsymfony/rest-bundle": "~2.1",
58 "jms/serializer-bundle": "~1.0", 58 "jms/serializer-bundle": "~1.1",
59 "nelmio/api-doc-bundle": "~2.7", 59 "nelmio/api-doc-bundle": "~2.7",
60 "mgargano/simplehtmldom": "~1.5", 60 "mgargano/simplehtmldom": "~1.5",
61 "tecnickcom/tcpdf": "~6.2", 61 "tecnickcom/tc-lib-pdf": "dev-master",
62 "simplepie/simplepie": "~1.3.1", 62 "simplepie/simplepie": "~1.3.1",
63 "willdurand/hateoas-bundle": "~1.0", 63 "willdurand/hateoas-bundle": "~1.0",
64 "htmlawed/htmlawed": "~1.1.19", 64 "htmlawed/htmlawed": "~1.1.19",
diff --git a/docs/en/user/configuration.rst b/docs/en/user/configuration.rst
index f74924df..e7055a14 100644
--- a/docs/en/user/configuration.rst
+++ b/docs/en/user/configuration.rst
@@ -50,6 +50,8 @@ User information
50 50
51You can change your name, your email address and enable ``Two factor authentication``. 51You can change your name, your email address and enable ``Two factor authentication``.
52 52
53If the wallabag instance has more than one enabled user, you can delete your account here. **Take care, we delete all your data**.
54
53Two factor authentication 55Two factor authentication
54~~~~~~~~~~~~~~~~~~~~~~~~~ 56~~~~~~~~~~~~~~~~~~~~~~~~~
55 57
diff --git a/docs/en/user/parameters.rst b/docs/en/user/parameters.rst
index 6cbd5ae4..c35cf3b8 100644
--- a/docs/en/user/parameters.rst
+++ b/docs/en/user/parameters.rst
@@ -12,6 +12,7 @@ What is the meaning of the parameters?
12 "database_path", "``""%kernel.root_dir%/../data/db/wallabag.sqlite""``", "only for SQLite, define where to put the database file. Leave it empty for other database" 12 "database_path", "``""%kernel.root_dir%/../data/db/wallabag.sqlite""``", "only for SQLite, define where to put the database file. Leave it empty for other database"
13 "database_table_prefix", "wallabag_", "all wallabag's tables will be prefixed with that string. You can include a ``_`` for clarity" 13 "database_table_prefix", "wallabag_", "all wallabag's tables will be prefixed with that string. You can include a ``_`` for clarity"
14 "database_socket", "null", "If your database is using a socket instead of tcp, put the path of the socket (other connection parameters will then be ignored)" 14 "database_socket", "null", "If your database is using a socket instead of tcp, put the path of the socket (other connection parameters will then be ignored)"
15 "database_charset", "utf8mb4", "For PostgreSQL & SQLite you should use utf8, for MySQL use utf8mb4 which handle emoji"
15 16
16.. csv-table:: Configuration to send emails from wallabag 17.. csv-table:: Configuration to send emails from wallabag
17 :header: "name", "default", "description" 18 :header: "name", "default", "description"
diff --git a/docs/fr/user/configuration.rst b/docs/fr/user/configuration.rst
index 8bfe66f5..90eece11 100644
--- a/docs/fr/user/configuration.rst
+++ b/docs/fr/user/configuration.rst
@@ -51,6 +51,8 @@ Mon compte
51 51
52Vous pouvez ici modifier votre nom, votre adresse email et activer la ``Double authentification``. 52Vous pouvez ici modifier votre nom, votre adresse email et activer la ``Double authentification``.
53 53
54Si l'instance de wallabag compte plus d'un utilisateur actif, vous pouvez supprimer ici votre compte. **Attention, nous supprimons toutes vos données**.
55
54Double authentification (2FA) 56Double authentification (2FA)
55~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 57~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
56 58
diff --git a/src/Wallabag/AnnotationBundle/Controller/WallabagAnnotationController.php b/src/Wallabag/AnnotationBundle/Controller/WallabagAnnotationController.php
index ad083e31..c13a034f 100644
--- a/src/Wallabag/AnnotationBundle/Controller/WallabagAnnotationController.php
+++ b/src/Wallabag/AnnotationBundle/Controller/WallabagAnnotationController.php
@@ -3,9 +3,8 @@
3namespace Wallabag\AnnotationBundle\Controller; 3namespace Wallabag\AnnotationBundle\Controller;
4 4
5use FOS\RestBundle\Controller\FOSRestController; 5use FOS\RestBundle\Controller\FOSRestController;
6use Nelmio\ApiDocBundle\Annotation\ApiDoc; 6use Symfony\Component\HttpFoundation\JsonResponse;
7use Symfony\Component\HttpFoundation\Request; 7use Symfony\Component\HttpFoundation\Request;
8use Symfony\Component\HttpFoundation\Response;
9use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; 8use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
10use Wallabag\AnnotationBundle\Entity\Annotation; 9use Wallabag\AnnotationBundle\Entity\Annotation;
11use Wallabag\CoreBundle\Entity\Entry; 10use Wallabag\CoreBundle\Entity\Entry;
@@ -15,42 +14,35 @@ class WallabagAnnotationController extends FOSRestController
15 /** 14 /**
16 * Retrieve annotations for an entry. 15 * Retrieve annotations for an entry.
17 * 16 *
18 * @ApiDoc( 17 * @param Entry $entry
19 * requirements={ 18 *
20 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"} 19 * @see Wallabag\ApiBundle\Controller\WallabagRestController
21 * }
22 * )
23 * 20 *
24 * @return Response 21 * @return JsonResponse
25 */ 22 */
26 public function getAnnotationsAction(Entry $entry) 23 public function getAnnotationsAction(Entry $entry)
27 { 24 {
28 $annotationRows = $this 25 $annotationRows = $this
29 ->getDoctrine() 26 ->getDoctrine()
30 ->getRepository('WallabagAnnotationBundle:Annotation') 27 ->getRepository('WallabagAnnotationBundle:Annotation')
31 ->findAnnotationsByPageId($entry->getId(), $this->getUser()->getId()); 28 ->findAnnotationsByPageId($entry->getId(), $this->getUser()->getId());
32 $total = count($annotationRows); 29 $total = count($annotationRows);
33 $annotations = ['total' => $total, 'rows' => $annotationRows]; 30 $annotations = ['total' => $total, 'rows' => $annotationRows];
34 31
35 $json = $this->get('serializer')->serialize($annotations, 'json'); 32 $json = $this->get('serializer')->serialize($annotations, 'json');
36 33
37 return $this->renderJsonResponse($json); 34 return (new JsonResponse())->setJson($json);
38 } 35 }
39 36
40 /** 37 /**
41 * Creates a new annotation. 38 * Creates a new annotation.
42 * 39 *
43 * @param Entry $entry 40 * @param Request $request
41 * @param Entry $entry
44 * 42 *
45 * @ApiDoc( 43 * @return JsonResponse
46 * requirements={
47 * {"name"="ranges", "dataType"="array", "requirement"="\w+", "description"="The range array for the annotation"},
48 * {"name"="quote", "dataType"="string", "required"=false, "description"="Optional, quote for the annotation"},
49 * {"name"="text", "dataType"="string", "required"=true, "description"=""},
50 * }
51 * )
52 * 44 *
53 * @return Response 45 * @see Wallabag\ApiBundle\Controller\WallabagRestController
54 */ 46 */
55 public function postAnnotationAction(Request $request, Entry $entry) 47 public function postAnnotationAction(Request $request, Entry $entry)
56 { 48 {
@@ -75,21 +67,20 @@ class WallabagAnnotationController extends FOSRestController
75 67
76 $json = $this->get('serializer')->serialize($annotation, 'json'); 68 $json = $this->get('serializer')->serialize($annotation, 'json');
77 69
78 return $this->renderJsonResponse($json); 70 return (new JsonResponse())->setJson($json);
79 } 71 }
80 72
81 /** 73 /**
82 * Updates an annotation. 74 * Updates an annotation.
83 * 75 *
84 * @ApiDoc( 76 * @see Wallabag\ApiBundle\Controller\WallabagRestController
85 * requirements={
86 * {"name"="annotation", "dataType"="string", "requirement"="\w+", "description"="The annotation ID"}
87 * }
88 * )
89 * 77 *
90 * @ParamConverter("annotation", class="WallabagAnnotationBundle:Annotation") 78 * @ParamConverter("annotation", class="WallabagAnnotationBundle:Annotation")
91 * 79 *
92 * @return Response 80 * @param Annotation $annotation
81 * @param Request $request
82 *
83 * @return JsonResponse
93 */ 84 */
94 public function putAnnotationAction(Annotation $annotation, Request $request) 85 public function putAnnotationAction(Annotation $annotation, Request $request)
95 { 86 {
@@ -104,21 +95,19 @@ class WallabagAnnotationController extends FOSRestController
104 95
105 $json = $this->get('serializer')->serialize($annotation, 'json'); 96 $json = $this->get('serializer')->serialize($annotation, 'json');
106 97
107 return $this->renderJsonResponse($json); 98 return (new JsonResponse())->setJson($json);
108 } 99 }
109 100
110 /** 101 /**
111 * Removes an annotation. 102 * Removes an annotation.
112 * 103 *
113 * @ApiDoc( 104 * @see Wallabag\ApiBundle\Controller\WallabagRestController
114 * requirements={
115 * {"name"="annotation", "dataType"="string", "requirement"="\w+", "description"="The annotation ID"}
116 * }
117 * )
118 * 105 *
119 * @ParamConverter("annotation", class="WallabagAnnotationBundle:Annotation") 106 * @ParamConverter("annotation", class="WallabagAnnotationBundle:Annotation")
120 * 107 *
121 * @return Response 108 * @param Annotation $annotation
109 *
110 * @return JsonResponse
122 */ 111 */
123 public function deleteAnnotationAction(Annotation $annotation) 112 public function deleteAnnotationAction(Annotation $annotation)
124 { 113 {
@@ -128,19 +117,6 @@ class WallabagAnnotationController extends FOSRestController
128 117
129 $json = $this->get('serializer')->serialize($annotation, 'json'); 118 $json = $this->get('serializer')->serialize($annotation, 'json');
130 119
131 return $this->renderJsonResponse($json); 120 return (new JsonResponse())->setJson($json);
132 }
133
134 /**
135 * Send a JSON Response.
136 * We don't use the Symfony JsonRespone, because it takes an array as parameter instead of a JSON string.
137 *
138 * @param string $json
139 *
140 * @return Response
141 */
142 private function renderJsonResponse($json, $code = 200)
143 {
144 return new Response($json, $code, ['application/json']);
145 } 121 }
146} 122}
diff --git a/src/Wallabag/AnnotationBundle/Entity/Annotation.php b/src/Wallabag/AnnotationBundle/Entity/Annotation.php
index c48d8731..0838f5aa 100644
--- a/src/Wallabag/AnnotationBundle/Entity/Annotation.php
+++ b/src/Wallabag/AnnotationBundle/Entity/Annotation.php
@@ -82,7 +82,7 @@ class Annotation
82 * @Exclude 82 * @Exclude
83 * 83 *
84 * @ORM\ManyToOne(targetEntity="Wallabag\CoreBundle\Entity\Entry", inversedBy="annotations") 84 * @ORM\ManyToOne(targetEntity="Wallabag\CoreBundle\Entity\Entry", inversedBy="annotations")
85 * @ORM\JoinColumn(name="entry_id", referencedColumnName="id") 85 * @ORM\JoinColumn(name="entry_id", referencedColumnName="id", onDelete="cascade")
86 */ 86 */
87 private $entry; 87 private $entry;
88 88
diff --git a/src/Wallabag/AnnotationBundle/Repository/AnnotationRepository.php b/src/Wallabag/AnnotationBundle/Repository/AnnotationRepository.php
index 5f7da70e..8d3f07ee 100644
--- a/src/Wallabag/AnnotationBundle/Repository/AnnotationRepository.php
+++ b/src/Wallabag/AnnotationBundle/Repository/AnnotationRepository.php
@@ -50,7 +50,8 @@ class AnnotationRepository extends EntityRepository
50 { 50 {
51 return $this->createQueryBuilder('a') 51 return $this->createQueryBuilder('a')
52 ->andWhere('a.id = :annotationId')->setParameter('annotationId', $annotationId) 52 ->andWhere('a.id = :annotationId')->setParameter('annotationId', $annotationId)
53 ->getQuery()->getSingleResult() 53 ->getQuery()
54 ->getSingleResult()
54 ; 55 ;
55 } 56 }
56 57
@@ -67,7 +68,8 @@ class AnnotationRepository extends EntityRepository
67 return $this->createQueryBuilder('a') 68 return $this->createQueryBuilder('a')
68 ->where('a.entry = :entryId')->setParameter('entryId', $entryId) 69 ->where('a.entry = :entryId')->setParameter('entryId', $entryId)
69 ->andwhere('a.user = :userId')->setParameter('userId', $userId) 70 ->andwhere('a.user = :userId')->setParameter('userId', $userId)
70 ->getQuery()->getResult() 71 ->getQuery()
72 ->getResult()
71 ; 73 ;
72 } 74 }
73 75
@@ -106,4 +108,18 @@ class AnnotationRepository extends EntityRepository
106 ->getQuery() 108 ->getQuery()
107 ->getSingleResult(); 109 ->getSingleResult();
108 } 110 }
111
112 /**
113 * Remove all annotations for a user id.
114 * Used when a user want to reset all informations.
115 *
116 * @param int $userId
117 */
118 public function removeAllByUserId($userId)
119 {
120 $this->getEntityManager()
121 ->createQuery('DELETE FROM Wallabag\AnnotationBundle\Entity\Annotation a WHERE a.user = :userId')
122 ->setParameter('userId', $userId)
123 ->execute();
124 }
109} 125}
diff --git a/src/Wallabag/ApiBundle/Controller/WallabagRestController.php b/src/Wallabag/ApiBundle/Controller/WallabagRestController.php
index 9997913d..a73d44ca 100644
--- a/src/Wallabag/ApiBundle/Controller/WallabagRestController.php
+++ b/src/Wallabag/ApiBundle/Controller/WallabagRestController.php
@@ -3,15 +3,17 @@
3namespace Wallabag\ApiBundle\Controller; 3namespace Wallabag\ApiBundle\Controller;
4 4
5use FOS\RestBundle\Controller\FOSRestController; 5use FOS\RestBundle\Controller\FOSRestController;
6use Hateoas\Configuration\Route; 6use Hateoas\Configuration\Route as HateoasRoute;
7use Hateoas\Representation\Factory\PagerfantaFactory; 7use Hateoas\Representation\Factory\PagerfantaFactory;
8use Nelmio\ApiDocBundle\Annotation\ApiDoc; 8use Nelmio\ApiDocBundle\Annotation\ApiDoc;
9use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
9use Symfony\Component\HttpFoundation\Request; 10use Symfony\Component\HttpFoundation\Request;
10use Symfony\Component\HttpFoundation\JsonResponse; 11use Symfony\Component\HttpFoundation\JsonResponse;
11use Symfony\Component\Routing\Generator\UrlGeneratorInterface; 12use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
12use Symfony\Component\Security\Core\Exception\AccessDeniedException; 13use Symfony\Component\Security\Core\Exception\AccessDeniedException;
13use Wallabag\CoreBundle\Entity\Entry; 14use Wallabag\CoreBundle\Entity\Entry;
14use Wallabag\CoreBundle\Entity\Tag; 15use Wallabag\CoreBundle\Entity\Tag;
16use Wallabag\AnnotationBundle\Entity\Annotation;
15 17
16class WallabagRestController extends FOSRestController 18class WallabagRestController extends FOSRestController
17{ 19{
@@ -115,7 +117,7 @@ class WallabagRestController extends FOSRestController
115 $pagerfantaFactory = new PagerfantaFactory('page', 'perPage'); 117 $pagerfantaFactory = new PagerfantaFactory('page', 'perPage');
116 $paginatedCollection = $pagerfantaFactory->createRepresentation( 118 $paginatedCollection = $pagerfantaFactory->createRepresentation(
117 $pager, 119 $pager,
118 new Route( 120 new HateoasRoute(
119 'api_get_entries', 121 'api_get_entries',
120 [ 122 [
121 'archive' => $isArchived, 123 'archive' => $isArchived,
@@ -158,6 +160,28 @@ class WallabagRestController extends FOSRestController
158 } 160 }
159 161
160 /** 162 /**
163 * Retrieve a single entry as a predefined format.
164 *
165 * @ApiDoc(
166 * requirements={
167 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
168 * }
169 * )
170 *
171 * @return Response
172 */
173 public function getEntryExportAction(Entry $entry, Request $request)
174 {
175 $this->validateAuthentication();
176 $this->validateUserAccess($entry->getUser()->getId());
177
178 return $this->get('wallabag_core.helper.entries_export')
179 ->setEntries($entry)
180 ->updateTitle('entry')
181 ->exportAs($request->attributes->get('_format'));
182 }
183
184 /**
161 * Create an entry. 185 * Create an entry.
162 * 186 *
163 * @ApiDoc( 187 * @ApiDoc(
@@ -496,6 +520,104 @@ class WallabagRestController extends FOSRestController
496 } 520 }
497 521
498 /** 522 /**
523 * Retrieve annotations for an entry.
524 *
525 * @ApiDoc(
526 * requirements={
527 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
528 * }
529 * )
530 *
531 * @param Entry $entry
532 *
533 * @return JsonResponse
534 */
535 public function getAnnotationsAction(Entry $entry)
536 {
537 $this->validateAuthentication();
538
539 return $this->forward('WallabagAnnotationBundle:WallabagAnnotation:getAnnotations', [
540 'entry' => $entry,
541 ]);
542 }
543
544 /**
545 * Creates a new annotation.
546 *
547 * @ApiDoc(
548 * requirements={
549 * {"name"="ranges", "dataType"="array", "requirement"="\w+", "description"="The range array for the annotation"},
550 * {"name"="quote", "dataType"="string", "required"=false, "description"="Optional, quote for the annotation"},
551 * {"name"="text", "dataType"="string", "required"=true, "description"=""},
552 * }
553 * )
554 *
555 * @param Request $request
556 * @param Entry $entry
557 *
558 * @return JsonResponse
559 */
560 public function postAnnotationAction(Request $request, Entry $entry)
561 {
562 $this->validateAuthentication();
563
564 return $this->forward('WallabagAnnotationBundle:WallabagAnnotation:postAnnotation', [
565 'request' => $request,
566 'entry' => $entry,
567 ]);
568 }
569
570 /**
571 * Updates an annotation.
572 *
573 * @ApiDoc(
574 * requirements={
575 * {"name"="annotation", "dataType"="string", "requirement"="\w+", "description"="The annotation ID"}
576 * }
577 * )
578 *
579 * @ParamConverter("annotation", class="WallabagAnnotationBundle:Annotation")
580 *
581 * @param Annotation $annotation
582 * @param Request $request
583 *
584 * @return JsonResponse
585 */
586 public function putAnnotationAction(Annotation $annotation, Request $request)
587 {
588 $this->validateAuthentication();
589
590 return $this->forward('WallabagAnnotationBundle:WallabagAnnotation:putAnnotation', [
591 'annotation' => $annotation,
592 'request' => $request,
593 ]);
594 }
595
596 /**
597 * Removes an annotation.
598 *
599 * @ApiDoc(
600 * requirements={
601 * {"name"="annotation", "dataType"="string", "requirement"="\w+", "description"="The annotation ID"}
602 * }
603 * )
604 *
605 * @ParamConverter("annotation", class="WallabagAnnotationBundle:Annotation")
606 *
607 * @param Annotation $annotation
608 *
609 * @return JsonResponse
610 */
611 public function deleteAnnotationAction(Annotation $annotation)
612 {
613 $this->validateAuthentication();
614
615 return $this->forward('WallabagAnnotationBundle:WallabagAnnotation:deleteAnnotation', [
616 'annotation' => $annotation,
617 ]);
618 }
619
620 /**
499 * Retrieve version number. 621 * Retrieve version number.
500 * 622 *
501 * @ApiDoc() 623 * @ApiDoc()
diff --git a/src/Wallabag/ApiBundle/Resources/config/routing_rest.yml b/src/Wallabag/ApiBundle/Resources/config/routing_rest.yml
index 5f43f971..35f8b2c1 100644
--- a/src/Wallabag/ApiBundle/Resources/config/routing_rest.yml
+++ b/src/Wallabag/ApiBundle/Resources/config/routing_rest.yml
@@ -1,4 +1,4 @@
1entries: 1api:
2 type: rest 2 type: rest
3 resource: "WallabagApiBundle:WallabagRest" 3 resource: "WallabagApiBundle:WallabagRest"
4 name_prefix: api_ 4 name_prefix: api_
diff --git a/src/Wallabag/CoreBundle/Command/InstallCommand.php b/src/Wallabag/CoreBundle/Command/InstallCommand.php
index 857a8b4c..277f8524 100644
--- a/src/Wallabag/CoreBundle/Command/InstallCommand.php
+++ b/src/Wallabag/CoreBundle/Command/InstallCommand.php
@@ -40,7 +40,7 @@ class InstallCommand extends ContainerAwareCommand
40 { 40 {
41 $this 41 $this
42 ->setName('wallabag:install') 42 ->setName('wallabag:install')
43 ->setDescription('Wallabag installer.') 43 ->setDescription('wallabag installer.')
44 ->addOption( 44 ->addOption(
45 'reset', 45 'reset',
46 null, 46 null,
@@ -55,7 +55,7 @@ class InstallCommand extends ContainerAwareCommand
55 $this->defaultInput = $input; 55 $this->defaultInput = $input;
56 $this->defaultOutput = $output; 56 $this->defaultOutput = $output;
57 57
58 $output->writeln('<info>Installing Wallabag...</info>'); 58 $output->writeln('<info>Installing wallabag...</info>');
59 $output->writeln(''); 59 $output->writeln('');
60 60
61 $this 61 $this
@@ -65,7 +65,7 @@ class InstallCommand extends ContainerAwareCommand
65 ->setupConfig() 65 ->setupConfig()
66 ; 66 ;
67 67
68 $output->writeln('<info>Wallabag has been successfully installed.</info>'); 68 $output->writeln('<info>wallabag has been successfully installed.</info>');
69 $output->writeln('<comment>Just execute `php bin/console server:run --env=prod` for using wallabag: http://localhost:8000</comment>'); 69 $output->writeln('<comment>Just execute `php bin/console server:run --env=prod` for using wallabag: http://localhost:8000</comment>');
70 } 70 }
71 71
@@ -77,7 +77,7 @@ class InstallCommand extends ContainerAwareCommand
77 77
78 // testing if database driver exists 78 // testing if database driver exists
79 $fulfilled = true; 79 $fulfilled = true;
80 $label = '<comment>PDO Driver</comment>'; 80 $label = '<comment>PDO Driver (%s)</comment>';
81 $status = '<info>OK!</info>'; 81 $status = '<info>OK!</info>';
82 $help = ''; 82 $help = '';
83 83
@@ -87,7 +87,7 @@ class InstallCommand extends ContainerAwareCommand
87 $help = 'Database driver "'.$this->getContainer()->getParameter('database_driver').'" is not installed.'; 87 $help = 'Database driver "'.$this->getContainer()->getParameter('database_driver').'" is not installed.';
88 } 88 }
89 89
90 $rows[] = [$label, $status, $help]; 90 $rows[] = [sprintf($label, $this->getContainer()->getParameter('database_driver')), $status, $help];
91 91
92 // testing if connection to the database can be etablished 92 // testing if connection to the database can be etablished
93 $label = '<comment>Database connection</comment>'; 93 $label = '<comment>Database connection</comment>';
@@ -95,7 +95,8 @@ class InstallCommand extends ContainerAwareCommand
95 $help = ''; 95 $help = '';
96 96
97 try { 97 try {
98 $this->getContainer()->get('doctrine')->getManager()->getConnection()->connect(); 98 $conn = $this->getContainer()->get('doctrine')->getManager()->getConnection();
99 $conn->connect();
99 } catch (\Exception $e) { 100 } catch (\Exception $e) {
100 if (false === strpos($e->getMessage(), 'Unknown database') 101 if (false === strpos($e->getMessage(), 'Unknown database')
101 && false === strpos($e->getMessage(), 'database "'.$this->getContainer()->getParameter('database_name').'" does not exist')) { 102 && false === strpos($e->getMessage(), 'database "'.$this->getContainer()->getParameter('database_name').'" does not exist')) {
@@ -107,6 +108,21 @@ class InstallCommand extends ContainerAwareCommand
107 108
108 $rows[] = [$label, $status, $help]; 109 $rows[] = [$label, $status, $help];
109 110
111 // now check if MySQL isn't too old to handle utf8mb4
112 if ($conn->isConnected() && $conn->getDatabasePlatform() instanceof \Doctrine\DBAL\Platforms\MySqlPlatform) {
113 $version = $conn->query('select version()')->fetchColumn();
114 $minimalVersion = '5.5.4';
115
116 if (false === version_compare($version, $minimalVersion, '>')) {
117 $fulfilled = false;
118 $rows[] = [
119 '<comment>Database version</comment>',
120 '<error>ERROR!</error>',
121 'Your MySQL version ('.$version.') is too old, consider upgrading ('.$minimalVersion.'+).',
122 ];
123 }
124 }
125
110 foreach ($this->functionExists as $functionRequired) { 126 foreach ($this->functionExists as $functionRequired) {
111 $label = '<comment>'.$functionRequired.'</comment>'; 127 $label = '<comment>'.$functionRequired.'</comment>';
112 $status = '<info>OK!</info>'; 128 $status = '<info>OK!</info>';
@@ -131,7 +147,7 @@ class InstallCommand extends ContainerAwareCommand
131 throw new \RuntimeException('Some system requirements are not fulfilled. Please check output messages and fix them.'); 147 throw new \RuntimeException('Some system requirements are not fulfilled. Please check output messages and fix them.');
132 } 148 }
133 149
134 $this->defaultOutput->writeln('<info>Success! Your system can run Wallabag properly.</info>'); 150 $this->defaultOutput->writeln('<info>Success! Your system can run wallabag properly.</info>');
135 151
136 $this->defaultOutput->writeln(''); 152 $this->defaultOutput->writeln('');
137 153
diff --git a/src/Wallabag/CoreBundle/Controller/ConfigController.php b/src/Wallabag/CoreBundle/Controller/ConfigController.php
index 91cdcae5..8d391917 100644
--- a/src/Wallabag/CoreBundle/Controller/ConfigController.php
+++ b/src/Wallabag/CoreBundle/Controller/ConfigController.php
@@ -7,6 +7,7 @@ use Symfony\Bundle\FrameworkBundle\Controller\Controller;
7use Symfony\Component\HttpFoundation\JsonResponse; 7use Symfony\Component\HttpFoundation\JsonResponse;
8use Symfony\Component\HttpFoundation\RedirectResponse; 8use Symfony\Component\HttpFoundation\RedirectResponse;
9use Symfony\Component\HttpFoundation\Request; 9use Symfony\Component\HttpFoundation\Request;
10use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
10use Wallabag\CoreBundle\Entity\Config; 11use Wallabag\CoreBundle\Entity\Config;
11use Wallabag\CoreBundle\Entity\TaggingRule; 12use Wallabag\CoreBundle\Entity\TaggingRule;
12use Wallabag\CoreBundle\Form\Type\ConfigType; 13use Wallabag\CoreBundle\Form\Type\ConfigType;
@@ -148,6 +149,9 @@ class ConfigController extends Controller
148 'token' => $config->getRssToken(), 149 'token' => $config->getRssToken(),
149 ], 150 ],
150 'twofactor_auth' => $this->getParameter('twofactor_auth'), 151 'twofactor_auth' => $this->getParameter('twofactor_auth'),
152 'enabled_users' => $this->getDoctrine()
153 ->getRepository('WallabagUserBundle:User')
154 ->getSumEnabledUsers(),
151 ]); 155 ]);
152 } 156 }
153 157
@@ -221,6 +225,80 @@ class ConfigController extends Controller
221 } 225 }
222 226
223 /** 227 /**
228 * Remove all annotations OR tags OR entries for the current user.
229 *
230 * @Route("/reset/{type}", requirements={"id" = "annotations|tags|entries"}, name="config_reset")
231 *
232 * @return RedirectResponse
233 */
234 public function resetAction($type)
235 {
236 $em = $this->getDoctrine()->getManager();
237
238 switch ($type) {
239 case 'annotations':
240 $this->getDoctrine()
241 ->getRepository('WallabagAnnotationBundle:Annotation')
242 ->removeAllByUserId($this->getUser()->getId());
243 break;
244
245 case 'tags':
246 $this->removeAllTagsByUserId($this->getUser()->getId());
247 break;
248
249 case 'entries':
250 // SQLite doesn't care about cascading remove, so we need to manually remove associated stuf
251 // otherwise they won't be removed ...
252 if ($this->get('doctrine')->getConnection()->getDriver() instanceof \Doctrine\DBAL\Driver\PDOSqlite\Driver) {
253 $this->getDoctrine()->getRepository('WallabagAnnotationBundle:Annotation')->removeAllByUserId($this->getUser()->getId());
254 }
255
256 // manually remove tags to avoid orphan tag
257 $this->removeAllTagsByUserId($this->getUser()->getId());
258
259 $this->getDoctrine()
260 ->getRepository('WallabagCoreBundle:Entry')
261 ->removeAllByUserId($this->getUser()->getId());
262 }
263
264 $this->get('session')->getFlashBag()->add(
265 'notice',
266 'flashes.config.notice.'.$type.'_reset'
267 );
268
269 return $this->redirect($this->generateUrl('config').'#set3');
270 }
271
272 /**
273 * Remove all tags for a given user and cleanup orphan tags.
274 *
275 * @param int $userId
276 */
277 private function removeAllTagsByUserId($userId)
278 {
279 $tags = $this->getDoctrine()->getRepository('WallabagCoreBundle:Tag')->findAllTags($userId);
280
281 if (empty($tags)) {
282 return;
283 }
284
285 $this->getDoctrine()
286 ->getRepository('WallabagCoreBundle:Entry')
287 ->removeTags($userId, $tags);
288
289 // cleanup orphan tags
290 $em = $this->getDoctrine()->getManager();
291
292 foreach ($tags as $tag) {
293 if (count($tag->getEntries()) === 0) {
294 $em->remove($tag);
295 }
296 }
297
298 $em->flush();
299 }
300
301 /**
224 * Validate that a rule can be edited/deleted by the current user. 302 * Validate that a rule can be edited/deleted by the current user.
225 * 303 *
226 * @param TaggingRule $rule 304 * @param TaggingRule $rule
@@ -251,4 +329,37 @@ class ConfigController extends Controller
251 329
252 return $config; 330 return $config;
253 } 331 }
332
333 /**
334 * Delete account for current user.
335 *
336 * @Route("/account/delete", name="delete_account")
337 *
338 * @param Request $request
339 *
340 * @throws AccessDeniedHttpException
341 *
342 * @return \Symfony\Component\HttpFoundation\RedirectResponse
343 */
344 public function deleteAccountAction(Request $request)
345 {
346 $enabledUsers = $this->getDoctrine()
347 ->getRepository('WallabagUserBundle:User')
348 ->getSumEnabledUsers();
349
350 if ($enabledUsers <= 1) {
351 throw new AccessDeniedHttpException();
352 }
353
354 $user = $this->getUser();
355
356 // logout current user
357 $this->get('security.token_storage')->setToken(null);
358 $request->getSession()->invalidate();
359
360 $em = $this->get('fos_user.user_manager');
361 $em->deleteUser($user);
362
363 return $this->redirect($this->generateUrl('fos_user_security_login'));
364 }
254} 365}
diff --git a/src/Wallabag/CoreBundle/Controller/TagController.php b/src/Wallabag/CoreBundle/Controller/TagController.php
index 5acc6852..4542d484 100644
--- a/src/Wallabag/CoreBundle/Controller/TagController.php
+++ b/src/Wallabag/CoreBundle/Controller/TagController.php
@@ -90,15 +90,15 @@ class TagController extends Controller
90 90
91 $flatTags = []; 91 $flatTags = [];
92 92
93 foreach ($tags as $key => $tag) { 93 foreach ($tags as $tag) {
94 $nbEntries = $this->getDoctrine() 94 $nbEntries = $this->getDoctrine()
95 ->getRepository('WallabagCoreBundle:Entry') 95 ->getRepository('WallabagCoreBundle:Entry')
96 ->countAllEntriesByUserIdAndTagId($this->getUser()->getId(), $tag['id']); 96 ->countAllEntriesByUserIdAndTagId($this->getUser()->getId(), $tag->getId());
97 97
98 $flatTags[] = [ 98 $flatTags[] = [
99 'id' => $tag['id'], 99 'id' => $tag->getId(),
100 'label' => $tag['label'], 100 'label' => $tag->getLabel(),
101 'slug' => $tag['slug'], 101 'slug' => $tag->getSlug(),
102 'nbEntries' => $nbEntries, 102 'nbEntries' => $nbEntries,
103 ]; 103 ];
104 } 104 }
diff --git a/src/Wallabag/CoreBundle/Entity/Entry.php b/src/Wallabag/CoreBundle/Entity/Entry.php
index f2da3f4d..dd0f7e67 100644
--- a/src/Wallabag/CoreBundle/Entity/Entry.php
+++ b/src/Wallabag/CoreBundle/Entity/Entry.php
@@ -19,7 +19,7 @@ use Wallabag\AnnotationBundle\Entity\Annotation;
19 * 19 *
20 * @XmlRoot("entry") 20 * @XmlRoot("entry")
21 * @ORM\Entity(repositoryClass="Wallabag\CoreBundle\Repository\EntryRepository") 21 * @ORM\Entity(repositoryClass="Wallabag\CoreBundle\Repository\EntryRepository")
22 * @ORM\Table(name="`entry`") 22 * @ORM\Table(name="`entry`", options={"collate"="utf8mb4_unicode_ci", "charset"="utf8mb4"})
23 * @ORM\HasLifecycleCallbacks() 23 * @ORM\HasLifecycleCallbacks()
24 * @Hateoas\Relation("self", href = "expr('/api/entries/' ~ object.getId())") 24 * @Hateoas\Relation("self", href = "expr('/api/entries/' ~ object.getId())")
25 */ 25 */
@@ -190,10 +190,10 @@ class Entry
190 * @ORM\JoinTable( 190 * @ORM\JoinTable(
191 * name="entry_tag", 191 * name="entry_tag",
192 * joinColumns={ 192 * joinColumns={
193 * @ORM\JoinColumn(name="entry_id", referencedColumnName="id") 193 * @ORM\JoinColumn(name="entry_id", referencedColumnName="id", onDelete="cascade")
194 * }, 194 * },
195 * inverseJoinColumns={ 195 * inverseJoinColumns={
196 * @ORM\JoinColumn(name="tag_id", referencedColumnName="id") 196 * @ORM\JoinColumn(name="tag_id", referencedColumnName="id", onDelete="cascade")
197 * } 197 * }
198 * ) 198 * )
199 */ 199 */
diff --git a/src/Wallabag/CoreBundle/Repository/EntryRepository.php b/src/Wallabag/CoreBundle/Repository/EntryRepository.php
index cd2b47b9..14616d88 100644
--- a/src/Wallabag/CoreBundle/Repository/EntryRepository.php
+++ b/src/Wallabag/CoreBundle/Repository/EntryRepository.php
@@ -329,4 +329,18 @@ class EntryRepository extends EntityRepository
329 329
330 return $qb->getQuery()->getSingleScalarResult(); 330 return $qb->getQuery()->getSingleScalarResult();
331 } 331 }
332
333 /**
334 * Remove all entries for a user id.
335 * Used when a user want to reset all informations.
336 *
337 * @param int $userId
338 */
339 public function removeAllByUserId($userId)
340 {
341 $this->getEntityManager()
342 ->createQuery('DELETE FROM Wallabag\CoreBundle\Entity\Entry e WHERE e.user = :userId')
343 ->setParameter('userId', $userId)
344 ->execute();
345 }
332} 346}
diff --git a/src/Wallabag/CoreBundle/Repository/TagRepository.php b/src/Wallabag/CoreBundle/Repository/TagRepository.php
index e76878d4..81445989 100644
--- a/src/Wallabag/CoreBundle/Repository/TagRepository.php
+++ b/src/Wallabag/CoreBundle/Repository/TagRepository.php
@@ -34,6 +34,9 @@ class TagRepository extends EntityRepository
34 34
35 /** 35 /**
36 * Find all tags per user. 36 * Find all tags per user.
37 * Instead of just left joined on the Entry table, we select only id and group by id to avoid tag multiplication in results.
38 * Once we have all tags id, we can safely request them one by one.
39 * This'll still be fastest than the previous query.
37 * 40 *
38 * @param int $userId 41 * @param int $userId
39 * 42 *
@@ -41,15 +44,20 @@ class TagRepository extends EntityRepository
41 */ 44 */
42 public function findAllTags($userId) 45 public function findAllTags($userId)
43 { 46 {
44 return $this->createQueryBuilder('t') 47 $ids = $this->createQueryBuilder('t')
45 ->select('t.slug', 't.label', 't.id') 48 ->select('t.id')
46 ->leftJoin('t.entries', 'e') 49 ->leftJoin('t.entries', 'e')
47 ->where('e.user = :userId')->setParameter('userId', $userId) 50 ->where('e.user = :userId')->setParameter('userId', $userId)
48 ->groupBy('t.slug') 51 ->groupBy('t.id')
49 ->addGroupBy('t.label')
50 ->addGroupBy('t.id')
51 ->getQuery() 52 ->getQuery()
52 ->getArrayResult(); 53 ->getArrayResult();
54
55 $tags = [];
56 foreach ($ids as $id) {
57 $tags[] = $this->find($id);
58 }
59
60 return $tags;
53 } 61 }
54 62
55 /** 63 /**
diff --git a/src/Wallabag/CoreBundle/Resources/config/services.yml b/src/Wallabag/CoreBundle/Resources/config/services.yml
index 614488a6..cc5f9e9a 100644
--- a/src/Wallabag/CoreBundle/Resources/config/services.yml
+++ b/src/Wallabag/CoreBundle/Resources/config/services.yml
@@ -129,3 +129,10 @@ services:
129 arguments: 129 arguments:
130 - '@twig' 130 - '@twig'
131 - '%kernel.debug%' 131 - '%kernel.debug%'
132
133 wallabag_core.subscriber.sqlite_cascade_delete:
134 class: Wallabag\CoreBundle\Subscriber\SQLiteCascadeDeleteSubscriber
135 arguments:
136 - "@doctrine"
137 tags:
138 - { name: doctrine.event_subscriber }
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.da.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.da.yml
index 714ced14..c0595583 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.da.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.da.yml
@@ -88,6 +88,18 @@ config:
88 name_label: 'Navn' 88 name_label: 'Navn'
89 email_label: 'Emailadresse' 89 email_label: 'Emailadresse'
90 # twoFactorAuthentication_label: 'Two factor authentication' 90 # twoFactorAuthentication_label: 'Two factor authentication'
91 delete:
92 # title: Delete my account (a.k.a danger zone)
93 # description: If you remove your account, ALL your articles, ALL your tags, ALL your annotations and your account will be PERMANENTLY removed (it can't be UNDONE). You'll then be logged out.
94 # confirm: Are you really sure? (THIS CAN'T BE UNDONE)
95 # button: Delete my account
96 reset:
97 # title: Reset area (a.k.a danger zone)
98 # description: By hiting buttons below you'll have ability to remove some informations from your account. Be aware that these actions are IRREVERSIBLE.
99 # annotations: Remove ALL annotations
100 # tags: Remove ALL tags
101 # entries: Remove ALL entries
102 # confirm: Are you really really sure? (THIS CAN'T BE UNDONE)
91 form_password: 103 form_password:
92 old_password_label: 'Gammel adgangskode' 104 old_password_label: 'Gammel adgangskode'
93 new_password_label: 'Ny adgangskode' 105 new_password_label: 'Ny adgangskode'
@@ -460,6 +472,9 @@ flashes:
460 # tagging_rules_deleted: 'Tagging rule deleted' 472 # tagging_rules_deleted: 'Tagging rule deleted'
461 # user_added: 'User "%username%" added' 473 # user_added: 'User "%username%" added'
462 # rss_token_updated: 'RSS token updated' 474 # rss_token_updated: 'RSS token updated'
475 # annotations_reset: Annotations reset
476 # tags_reset: Tags reset
477 # entries_reset: Entries reset
463 entry: 478 entry:
464 notice: 479 notice:
465 # entry_already_saved: 'Entry already saved on %date%' 480 # entry_already_saved: 'Entry already saved on %date%'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.de.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.de.yml
index 57e49f84..0051da2f 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.de.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.de.yml
@@ -88,6 +88,18 @@ config:
88 name_label: 'Name' 88 name_label: 'Name'
89 email_label: 'E-Mail-Adresse' 89 email_label: 'E-Mail-Adresse'
90 twoFactorAuthentication_label: 'Zwei-Faktor-Authentifizierung' 90 twoFactorAuthentication_label: 'Zwei-Faktor-Authentifizierung'
91 delete:
92 # title: Delete my account (a.k.a danger zone)
93 # description: If you remove your account, ALL your articles, ALL your tags, ALL your annotations and your account will be PERMANENTLY removed (it can't be UNDONE). You'll then be logged out.
94 # confirm: Are you really sure? (THIS CAN'T BE UNDONE)
95 # button: Delete my account
96 reset:
97 # title: Reset area (a.k.a danger zone)
98 # description: By hiting buttons below you'll have ability to remove some informations from your account. Be aware that these actions are IRREVERSIBLE.
99 # annotations: Remove ALL annotations
100 # tags: Remove ALL tags
101 # entries: Remove ALL entries
102 # confirm: Are you really really sure? (THIS CAN'T BE UNDONE)
91 form_password: 103 form_password:
92 old_password_label: 'Altes Kennwort' 104 old_password_label: 'Altes Kennwort'
93 new_password_label: 'Neues Kennwort' 105 new_password_label: 'Neues Kennwort'
@@ -460,6 +472,9 @@ flashes:
460 tagging_rules_deleted: 'Tagging-Regel gelöscht' 472 tagging_rules_deleted: 'Tagging-Regel gelöscht'
461 user_added: 'Benutzer "%username%" erstellt' 473 user_added: 'Benutzer "%username%" erstellt'
462 rss_token_updated: 'RSS-Token aktualisiert' 474 rss_token_updated: 'RSS-Token aktualisiert'
475 # annotations_reset: Annotations reset
476 # tags_reset: Tags reset
477 # entries_reset: Entries reset
463 entry: 478 entry:
464 notice: 479 notice:
465 entry_already_saved: 'Eintrag bereits am %date% gespeichert' 480 entry_already_saved: 'Eintrag bereits am %date% gespeichert'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.en.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.en.yml
index 4a59c75e..462be556 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.en.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.en.yml
@@ -88,6 +88,18 @@ config:
88 name_label: 'Name' 88 name_label: 'Name'
89 email_label: 'Email' 89 email_label: 'Email'
90 twoFactorAuthentication_label: 'Two factor authentication' 90 twoFactorAuthentication_label: 'Two factor authentication'
91 delete:
92 title: Delete my account (a.k.a danger zone)
93 description: If you remove your account, ALL your articles, ALL your tags, ALL your annotations and your account will be PERMANENTLY removed (it can't be UNDONE). You'll then be logged out.
94 confirm: Are you really sure? (THIS CAN'T BE UNDONE)
95 button: Delete my account
96 reset:
97 title: Reset area (a.k.a danger zone)
98 description: By hiting buttons below you'll have ability to remove some informations from your account. Be aware that these actions are IRREVERSIBLE.
99 annotations: Remove ALL annotations
100 tags: Remove ALL tags
101 entries: Remove ALL entries
102 confirm: Are you really really sure? (THIS CAN'T BE UNDONE)
91 form_password: 103 form_password:
92 old_password_label: 'Current password' 104 old_password_label: 'Current password'
93 new_password_label: 'New password' 105 new_password_label: 'New password'
@@ -459,6 +471,9 @@ flashes:
459 tagging_rules_updated: 'Tagging rules updated' 471 tagging_rules_updated: 'Tagging rules updated'
460 tagging_rules_deleted: 'Tagging rule deleted' 472 tagging_rules_deleted: 'Tagging rule deleted'
461 rss_token_updated: 'RSS token updated' 473 rss_token_updated: 'RSS token updated'
474 annotations_reset: Annotations reset
475 tags_reset: Tags reset
476 entries_reset: Entries reset
462 entry: 477 entry:
463 notice: 478 notice:
464 entry_already_saved: 'Entry already saved on %date%' 479 entry_already_saved: 'Entry already saved on %date%'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.es.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.es.yml
index 1b1e0cb1..cfabe09f 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.es.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.es.yml
@@ -88,6 +88,18 @@ config:
88 name_label: 'Nombre' 88 name_label: 'Nombre'
89 email_label: 'Direccion e-mail' 89 email_label: 'Direccion e-mail'
90 twoFactorAuthentication_label: 'Autentificación de dos factores' 90 twoFactorAuthentication_label: 'Autentificación de dos factores'
91 delete:
92 # title: Delete my account (a.k.a danger zone)
93 # description: If you remove your account, ALL your articles, ALL your tags, ALL your annotations and your account will be PERMANENTLY removed (it can't be UNDONE). You'll then be logged out.
94 # confirm: Are you really sure? (THIS CAN'T BE UNDONE)
95 # button: Delete my account
96 reset:
97 # title: Reset area (a.k.a danger zone)
98 # description: By hiting buttons below you'll have ability to remove some informations from your account. Be aware that these actions are IRREVERSIBLE.
99 # annotations: Remove ALL annotations
100 # tags: Remove ALL tags
101 # entries: Remove ALL entries
102 # confirm: Are you really really sure? (THIS CAN'T BE UNDONE)
91 form_password: 103 form_password:
92 old_password_label: 'Contraseña actual' 104 old_password_label: 'Contraseña actual'
93 new_password_label: 'Nueva contraseña' 105 new_password_label: 'Nueva contraseña'
@@ -460,6 +472,9 @@ flashes:
460 tagging_rules_deleted: 'Regla de etiquetado actualizada' 472 tagging_rules_deleted: 'Regla de etiquetado actualizada'
461 user_added: 'Usuario "%username%" añadido' 473 user_added: 'Usuario "%username%" añadido'
462 rss_token_updated: 'RSS token actualizado' 474 rss_token_updated: 'RSS token actualizado'
475 # annotations_reset: Annotations reset
476 # tags_reset: Tags reset
477 # entries_reset: Entries reset
463 entry: 478 entry:
464 notice: 479 notice:
465 entry_already_saved: 'Entrada ya guardada por %fecha%' 480 entry_already_saved: 'Entrada ya guardada por %fecha%'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml
index 41dc8acf..07b5bee7 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml
@@ -88,6 +88,18 @@ config:
88 name_label: 'نام' 88 name_label: 'نام'
89 email_label: 'نشانی ایمیل' 89 email_label: 'نشانی ایمیل'
90 twoFactorAuthentication_label: 'تأیید ۲مرحله‌ای' 90 twoFactorAuthentication_label: 'تأیید ۲مرحله‌ای'
91 delete:
92 # title: Delete my account (a.k.a danger zone)
93 # description: If you remove your account, ALL your articles, ALL your tags, ALL your annotations and your account will be PERMANENTLY removed (it can't be UNDONE). You'll then be logged out.
94 # confirm: Are you really sure? (THIS CAN'T BE UNDONE)
95 # button: Delete my account
96 reset:
97 # title: Reset area (a.k.a danger zone)
98 # description: By hiting buttons below you'll have ability to remove some informations from your account. Be aware that these actions are IRREVERSIBLE.
99 # annotations: Remove ALL annotations
100 # tags: Remove ALL tags
101 # entries: Remove ALL entries
102 # confirm: Are you really really sure? (THIS CAN'T BE UNDONE)
91 form_password: 103 form_password:
92 old_password_label: 'رمز قدیمی' 104 old_password_label: 'رمز قدیمی'
93 new_password_label: 'رمز تازه' 105 new_password_label: 'رمز تازه'
@@ -459,6 +471,9 @@ flashes:
459 tagging_rules_deleted: 'قانون برچسب‌گذاری پاک شد' 471 tagging_rules_deleted: 'قانون برچسب‌گذاری پاک شد'
460 user_added: 'کابر "%username%" افزوده شد' 472 user_added: 'کابر "%username%" افزوده شد'
461 rss_token_updated: 'کد آر-اس-اس به‌روز شد' 473 rss_token_updated: 'کد آر-اس-اس به‌روز شد'
474 # annotations_reset: Annotations reset
475 # tags_reset: Tags reset
476 # entries_reset: Entries reset
462 entry: 477 entry:
463 notice: 478 notice:
464 entry_already_saved: 'این مقاله در تاریخ %date% ذخیره شده بود' 479 entry_already_saved: 'این مقاله در تاریخ %date% ذخیره شده بود'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml
index 7fb9681d..db6f9f7e 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml
@@ -88,6 +88,18 @@ config:
88 name_label: 'Nom' 88 name_label: 'Nom'
89 email_label: 'Adresse e-mail' 89 email_label: 'Adresse e-mail'
90 twoFactorAuthentication_label: 'Double authentification' 90 twoFactorAuthentication_label: 'Double authentification'
91 delete:
92 title: Supprimer mon compte (attention danger !)
93 description: Si vous confirmez la suppression de votre compte, TOUS les articles, TOUS les tags, TOUTES les annotations et votre compte seront DÉFINITIVEMENT supprimé (c'est IRRÉVERSIBLE). Vous serez ensuite déconnecté.
94 confirm: Vous êtes vraiment sûr ? (C'EST IRRÉVERSIBLE)
95 button: 'Supprimer mon compte'
96 reset:
97 title: Réinitialisation (attention danger !)
98 description: En cliquant sur les boutons ci-dessous vous avez la possibilité de supprimer certaines informations de votre compte. Attention, ces actions sont IRRÉVERSIBLES !
99 annotations: Supprimer TOUTES les annotations
100 tags: Supprimer TOUS les tags
101 entries: Supprimer TOUS les articles
102 confirm: Êtes-vous vraiment vraiment sûr ? (C'EST IRRÉVERSIBLE)
91 form_password: 103 form_password:
92 old_password_label: 'Mot de passe actuel' 104 old_password_label: 'Mot de passe actuel'
93 new_password_label: 'Nouveau mot de passe' 105 new_password_label: 'Nouveau mot de passe'
@@ -386,7 +398,7 @@ developer:
386 field_grant_types: 'Type de privilège accordé' 398 field_grant_types: 'Type de privilège accordé'
387 no_client: 'Aucun client pour le moment' 399 no_client: 'Aucun client pour le moment'
388 remove: 400 remove:
389 warn_message_1: 'Vous avez la possibilité de supprimer le client %name%. Cette action est IRREVERSIBLE !' 401 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." 402 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%' 403 action: 'Supprimer le client %name%'
392 client: 404 client:
@@ -460,9 +472,12 @@ flashes:
460 tagging_rules_deleted: 'Règle supprimée' 472 tagging_rules_deleted: 'Règle supprimée'
461 user_added: 'Utilisateur "%username%" ajouté' 473 user_added: 'Utilisateur "%username%" ajouté'
462 rss_token_updated: 'Jeton RSS mis à jour' 474 rss_token_updated: 'Jeton RSS mis à jour'
475 annotations_reset: Annotations supprimées
476 tags_reset: Tags supprimés
477 entries_reset: Articles supprimés
463 entry: 478 entry:
464 notice: 479 notice:
465 entry_already_saved: 'Article déjà sauvergardé le %date%' 480 entry_already_saved: 'Article déjà sauvegardé le %date%'
466 entry_saved: 'Article enregistré' 481 entry_saved: 'Article enregistré'
467 entry_saved_failed: 'Article enregistré mais impossible de récupérer le contenu' 482 entry_saved_failed: 'Article enregistré mais impossible de récupérer le contenu'
468 entry_updated: 'Article mis à jour' 483 entry_updated: 'Article mis à jour'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.it.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.it.yml
index b279ae40..f1aff51a 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.it.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.it.yml
@@ -88,6 +88,18 @@ config:
88 name_label: 'Nome' 88 name_label: 'Nome'
89 email_label: 'E-mail' 89 email_label: 'E-mail'
90 twoFactorAuthentication_label: 'Two factor authentication' 90 twoFactorAuthentication_label: 'Two factor authentication'
91 delete:
92 # title: Delete my account (a.k.a danger zone)
93 # description: If you remove your account, ALL your articles, ALL your tags, ALL your annotations and your account will be PERMANENTLY removed (it can't be UNDONE). You'll then be logged out.
94 # confirm: Are you really sure? (THIS CAN'T BE UNDONE)
95 # button: Delete my account
96 reset:
97 # title: Reset area (a.k.a danger zone)
98 # description: By hiting buttons below you'll have ability to remove some informations from your account. Be aware that these actions are IRREVERSIBLE.
99 # annotations: Remove ALL annotations
100 # tags: Remove ALL tags
101 # entries: Remove ALL entries
102 # confirm: Are you really really sure? (THIS CAN'T BE UNDONE)
91 form_password: 103 form_password:
92 old_password_label: 'Password corrente' 104 old_password_label: 'Password corrente'
93 new_password_label: 'Nuova password' 105 new_password_label: 'Nuova password'
@@ -460,6 +472,9 @@ flashes:
460 tagging_rules_deleted: 'Regola di tagging aggiornate' 472 tagging_rules_deleted: 'Regola di tagging aggiornate'
461 user_added: 'Utente "%username%" aggiunto' 473 user_added: 'Utente "%username%" aggiunto'
462 rss_token_updated: 'RSS token aggiornato' 474 rss_token_updated: 'RSS token aggiornato'
475 # annotations_reset: Annotations reset
476 # tags_reset: Tags reset
477 # entries_reset: Entries reset
463 entry: 478 entry:
464 notice: 479 notice:
465 entry_already_saved: 'Contenuto già salvato in data %date%' 480 entry_already_saved: 'Contenuto già salvato in data %date%'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml
index a4659620..e0567d7e 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml
@@ -25,13 +25,13 @@ menu:
25 internal_settings: 'Configuracion interna' 25 internal_settings: 'Configuracion interna'
26 import: 'Importar' 26 import: 'Importar'
27 howto: 'Ajuda' 27 howto: 'Ajuda'
28 developer: 'Desvolopador' 28 developer: 'Desvolopaire'
29 logout: 'Desconnexion' 29 logout: 'Desconnexion'
30 about: 'A prepaus' 30 about: 'A prepaus'
31 search: 'Cercar' 31 search: 'Cercar'
32 save_link: 'Enregistrar un novèl article' 32 save_link: 'Enregistrar un novèl article'
33 back_to_unread: 'Tornar als articles pas legits' 33 back_to_unread: 'Tornar als articles pas legits'
34 # users_management: 'Users management' 34 users_management: 'Gestion dels utilizaires'
35 top: 35 top:
36 add_new_entry: 'Enregistrar un novèl article' 36 add_new_entry: 'Enregistrar un novèl article'
37 search: 'Cercar' 37 search: 'Cercar'
@@ -46,7 +46,7 @@ footer:
46 social: 'Social' 46 social: 'Social'
47 powered_by: 'propulsat per' 47 powered_by: 'propulsat per'
48 about: 'A prepaus' 48 about: 'A prepaus'
49 # stats: Since %user_creation% you read %nb_archives% articles. That is about %per_day% a day! 49 stats: "Dempuèi %user_creation% avètz legit %nb_archives% articles. Es a l'entorn de %per_day% per jorn !"
50 50
51config: 51config:
52 page_title: 'Configuracion' 52 page_title: 'Configuracion'
@@ -88,6 +88,18 @@ config:
88 name_label: 'Nom' 88 name_label: 'Nom'
89 email_label: 'Adreça de corrièl' 89 email_label: 'Adreça de corrièl'
90 twoFactorAuthentication_label: 'Dobla autentificacion' 90 twoFactorAuthentication_label: 'Dobla autentificacion'
91 delete:
92 # title: Delete my account (a.k.a danger zone)
93 # description: If you remove your account, ALL your articles, ALL your tags, ALL your annotations and your account will be PERMANENTLY removed (it can't be UNDONE). You'll then be logged out.
94 # confirm: Are you really sure? (THIS CAN'T BE UNDONE)
95 # button: Delete my account
96 reset:
97 # title: Reset area (a.k.a danger zone)
98 # description: By hiting buttons below you'll have ability to remove some informations from your account. Be aware that these actions are IRREVERSIBLE.
99 # annotations: Remove ALL annotations
100 # tags: Remove ALL tags
101 # entries: Remove ALL entries
102 # confirm: Are you really really sure? (THIS CAN'T BE UNDONE)
91 form_password: 103 form_password:
92 old_password_label: 'Senhal actual' 104 old_password_label: 'Senhal actual'
93 new_password_label: 'Senhal novèl' 105 new_password_label: 'Senhal novèl'
@@ -96,7 +108,7 @@ config:
96 if_label: 'se' 108 if_label: 'se'
97 then_tag_as_label: 'alara atribuir las etiquetas' 109 then_tag_as_label: 'alara atribuir las etiquetas'
98 delete_rule_label: 'suprimir' 110 delete_rule_label: 'suprimir'
99 # edit_rule_label: 'edit' 111 edit_rule_label: 'modificar'
100 rule_label: 'Règla' 112 rule_label: 'Règla'
101 tags_label: 'Etiquetas' 113 tags_label: 'Etiquetas'
102 faq: 114 faq:
@@ -209,7 +221,7 @@ entry:
209 is_public_label: 'Public' 221 is_public_label: 'Public'
210 save_label: 'Enregistrar' 222 save_label: 'Enregistrar'
211 public: 223 public:
212 # shared_by_wallabag: "This article has been shared by <a href='%wallabag_instance%'>wallabag</a>" 224 shared_by_wallabag: "Aqueste article es estat partejat per <a href='%wallabag_instance%'>wallabag</a>"
213 225
214about: 226about:
215 page_title: 'A prepaus' 227 page_title: 'A prepaus'
@@ -265,14 +277,14 @@ howto:
265 277
266quickstart: 278quickstart:
267 page_title: 'Per ben començar' 279 page_title: 'Per ben començar'
268 # more: 'More…' 280 more: 'Mai…'
269 intro: 281 intro:
270 title: 'Benvenguda sus wallabag !' 282 title: 'Benvenguda sus wallabag !'
271 paragraph_1: "Anem vos guidar per far lo torn de la proprietat e vos presentar unas fonccionalitats que vos poirián interessar per vos apropriar aquesta aisina." 283 paragraph_1: "Anem vos guidar per far lo torn de la proprietat e vos presentar unas fonccionalitats que vos poirián interessar per vos apropriar aquesta aisina."
272 paragraph_2: 'Seguètz-nos ' 284 paragraph_2: 'Seguètz-nos '
273 configure: 285 configure:
274 title: "Configuratz l'aplicacio" 286 title: "Configuratz l'aplicacion"
275 # description: 'In order to have an application which suits you, have a look into the configuration of wallabag.' 287 description: "Per fin d'aver una aplicacion que vos va ben, anatz veire la configuracion de wallabag."
276 language: "Cambiatz la lenga e l'estil de l'aplicacion" 288 language: "Cambiatz la lenga e l'estil de l'aplicacion"
277 rss: 'Activatz los fluxes RSS' 289 rss: 'Activatz los fluxes RSS'
278 tagging_rules: 'Escrivètz de règlas per classar automaticament vòstres articles' 290 tagging_rules: 'Escrivètz de règlas per classar automaticament vòstres articles'
@@ -286,7 +298,7 @@ quickstart:
286 import: 'Configurar los impòrt' 298 import: 'Configurar los impòrt'
287 first_steps: 299 first_steps:
288 title: 'Primièrs passes' 300 title: 'Primièrs passes'
289 # description: "Now wallabag is well configured, it's time to archive the web. You can click on the top right sign + to add a link." 301 description: "Ara wallabag es ben configurat, es lo moment d'archivar lo web. Podètz clicar sul signe + a man drecha amont per ajustar un ligam."
290 new_article: 'Ajustatz vòstre primièr article' 302 new_article: 'Ajustatz vòstre primièr article'
291 unread_articles: 'E racaptatz-lo !' 303 unread_articles: 'E racaptatz-lo !'
292 migrate: 304 migrate:
@@ -298,14 +310,14 @@ quickstart:
298 readability: 'Migrar dempuèi Readability' 310 readability: 'Migrar dempuèi Readability'
299 instapaper: 'Migrar dempuèi Instapaper' 311 instapaper: 'Migrar dempuèi Instapaper'
300 developer: 312 developer:
301 title: 'Pels desvolopadors' 313 title: 'Pels desvolopaires'
302 # description: 'We also thought to the developers: Docker, API, translations, etc.' 314 description: 'Avèm tanben pensat als desvolopaires : Docker, API, traduccions, etc.'
303 create_application: 'Crear vòstra aplicacion tèrça' 315 create_application: 'Crear vòstra aplicacion tèrça'
304 # use_docker: 'Use Docker to install wallabag' 316 use_docker: 'Utilizar Docker per installar wallabag'
305 docs: 317 docs:
306 title: 'Documentacion complèta' 318 title: 'Documentacion complèta'
307 # description: "There are so much features in wallabag. Don't hesitate to read the manual to know them and to learn how to use them." 319 description: "I a un fum de fonccionalitats dins wallabag. Esitetz pas a legir lo manual per las conéisser e aprendre a las utilizar."
308 annotate: 'Anotatar vòstre article' 320 annotate: 'Anotar vòstre article'
309 export: 'Convertissètz vòstres articles en ePub o en PDF' 321 export: 'Convertissètz vòstres articles en ePub o en PDF'
310 search_filters: "Aprenètz a utilizar lo motor de recèrca e los filtres per retrobar l'article que vos interèssa" 322 search_filters: "Aprenètz a utilizar lo motor de recèrca e los filtres per retrobar l'article que vos interèssa"
311 fetching_errors: "Qué far se mon article es pas recuperat coma cal ?" 323 fetching_errors: "Qué far se mon article es pas recuperat coma cal ?"
@@ -390,7 +402,7 @@ developer:
390 warn_message_2: "Se suprimissètz un client, totas las aplicacions que l'emplegan foncionaràn pas mai amb vòstre compte wallabag." 402 warn_message_2: "Se suprimissètz un client, totas las aplicacions que l'emplegan foncionaràn pas mai amb vòstre compte wallabag."
391 action: 'Suprimir aqueste client' 403 action: 'Suprimir aqueste client'
392 client: 404 client:
393 page_title: 'Desvlopador > Novèl client' 405 page_title: 'Desvolopaire > Novèl client'
394 page_description: "Anatz crear un novèl client. Mercés de cumplir l'url de redireccion cap a vòstra aplicacion." 406 page_description: "Anatz crear un novèl client. Mercés de cumplir l'url de redireccion cap a vòstra aplicacion."
395 form: 407 form:
396 name_label: "Nom del client" 408 name_label: "Nom del client"
@@ -398,7 +410,7 @@ developer:
398 save_label: 'Crear un novèl client' 410 save_label: 'Crear un novèl client'
399 action_back: 'Retorn' 411 action_back: 'Retorn'
400 client_parameter: 412 client_parameter:
401 page_title: 'Desvolopador > Los paramètres de vòstre client' 413 page_title: 'Desvolopaire > Los paramètres de vòstre client'
402 page_description: 'Vaquí los paramètres de vòstre client' 414 page_description: 'Vaquí los paramètres de vòstre client'
403 field_name: 'Nom del client' 415 field_name: 'Nom del client'
404 field_id: 'ID Client' 416 field_id: 'ID Client'
@@ -406,7 +418,7 @@ developer:
406 back: 'Retour' 418 back: 'Retour'
407 read_howto: 'Legir "cossí crear ma primièra aplicacion"' 419 read_howto: 'Legir "cossí crear ma primièra aplicacion"'
408 howto: 420 howto:
409 page_title: 'Desvolopador > Cossí crear ma primièra aplicacion' 421 page_title: 'Desvolopaire > Cossí crear ma primièra aplicacion'
410 description: 422 description:
411 paragraph_1: "Las comandas seguentas utilizan la <a href=\"https://github.com/jkbrzt/httpie\">bibliotèca HTTPie</a>. Asseguratz-vos que siasqueòu installadas abans de l'utilizar." 423 paragraph_1: "Las comandas seguentas utilizan la <a href=\"https://github.com/jkbrzt/httpie\">bibliotèca HTTPie</a>. Asseguratz-vos que siasqueòu installadas abans de l'utilizar."
412 paragraph_2: "Vos cal un geton per escambiar entre vòstra aplicacion e l'API de wallabar." 424 paragraph_2: "Vos cal un geton per escambiar entre vòstra aplicacion e l'API de wallabar."
@@ -419,31 +431,31 @@ developer:
419 back: 'Retorn' 431 back: 'Retorn'
420 432
421user: 433user:
422 # page_title: Users management 434 page_title: 'Gestion dels utilizaires'
423 # new_user: Create a new user 435 new_user: 'Crear un novèl utilizaire'
424 # edit_user: Edit an existing user 436 edit_user: 'Modificar un utilizaire existent'
425 # description: "Here you can manage all users (create, edit and delete)" 437 description: "Aquí podètz gerir totes los utilizaires (crear, modificar e suprimir)"
426 # list: 438 list:
427 # actions: Actions 439 actions: 'Accions'
428 # edit_action: Edit 440 edit_action: 'Modificar'
429 # yes: Yes 441 yes: 'Òc'
430 # no: No 442 no: 'Non'
431 # create_new_one: Create a new user 443 create_new_one: 'Crear un novèl utilizaire'
432 form: 444 form:
433 username_label: "Nom d'utilizaire" 445 username_label: "Nom d'utilizaire"
434 # name_label: 'Name' 446 name_label: 'Nom'
435 password_label: 'Senhal' 447 password_label: 'Senhal'
436 repeat_new_password_label: 'Confirmatz vòstre novèl senhal' 448 repeat_new_password_label: 'Confirmatz vòstre novèl senhal'
437 plain_password_label: 'Senhal en clar' 449 plain_password_label: 'Senhal en clar'
438 email_label: 'Adreça de corrièl' 450 email_label: 'Adreça de corrièl'
439 # enabled_label: 'Enabled' 451 enabled_label: 'Actiu'
440 # locked_label: 'Locked' 452 locked_label: 'Varrolhat'
441 # last_login_label: 'Last login' 453 last_login_label: 'Darrièra connexion'
442 # twofactor_label: Two factor authentication 454 twofactor_label: 'Autentificacion doble-factor'
443 # save: Save 455 save: 'Enregistrar'
444 # delete: Delete 456 delete: 'Suprimir'
445 # delete_confirm: Are you sure? 457 delete_confirm: 'Sètz segur ?'
446 # back_to_list: Back to list 458 back_to_list: 'Tornar a la lista'
447 459
448error: 460error:
449 # page_title: An error occurred 461 # page_title: An error occurred
@@ -458,8 +470,11 @@ flashes:
458 rss_updated: 'La configuracion dels fluxes RSS es ben estada mesa a jorn' 470 rss_updated: 'La configuracion dels fluxes RSS es ben estada mesa a jorn'
459 tagging_rules_updated: 'Règlas misa a jorn' 471 tagging_rules_updated: 'Règlas misa a jorn'
460 tagging_rules_deleted: 'Règla suprimida' 472 tagging_rules_deleted: 'Règla suprimida'
461 user_added: 'Utilizaire "%username%" apondut' 473 user_added: 'Utilizaire "%username%" ajustat'
462 rss_token_updated: 'Geton RSS mes a jorn' 474 rss_token_updated: 'Geton RSS mes a jorn'
475 # annotations_reset: Annotations reset
476 # tags_reset: Tags reset
477 # entries_reset: Entries reset
463 entry: 478 entry:
464 notice: 479 notice:
465 entry_already_saved: 'Article ja salvargardat lo %date%' 480 entry_already_saved: 'Article ja salvargardat lo %date%'
@@ -470,12 +485,12 @@ flashes:
470 entry_reloaded_failed: "L'article es estat cargat de nòu mai la recuperacion del contengut a fracassat" 485 entry_reloaded_failed: "L'article es estat cargat de nòu mai la recuperacion del contengut a fracassat"
471 entry_archived: 'Article marcat coma legit' 486 entry_archived: 'Article marcat coma legit'
472 entry_unarchived: 'Article marcat coma pas legit' 487 entry_unarchived: 'Article marcat coma pas legit'
473 entry_starred: 'Article apondut dins los favorits' 488 entry_starred: 'Article ajustat dins los favorits'
474 entry_unstarred: 'Article quitat dels favorits' 489 entry_unstarred: 'Article quitat dels favorits'
475 entry_deleted: 'Article suprimit' 490 entry_deleted: 'Article suprimit'
476 tag: 491 tag:
477 notice: 492 notice:
478 tag_added: 'Etiqueta aponduda' 493 tag_added: 'Etiqueta ajustada'
479 import: 494 import:
480 notice: 495 notice:
481 failed: "L'importacion a fracassat, mercés de tornar ensajar" 496 failed: "L'importacion a fracassat, mercés de tornar ensajar"
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml
index 798b39c2..8eef998b 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml
@@ -88,6 +88,18 @@ config:
88 name_label: 'Nazwa' 88 name_label: 'Nazwa'
89 email_label: 'Adres email' 89 email_label: 'Adres email'
90 twoFactorAuthentication_label: 'Autoryzacja dwuetapowa' 90 twoFactorAuthentication_label: 'Autoryzacja dwuetapowa'
91 delete:
92 title: Usuń moje konto (niebezpieczna strefa !)
93 description: Jeżeli usuniesz swoje konto, wszystkie twoje artykuły, tagi, adnotacje, oraz konto zostaną trwale usunięte (operacja jest NIEODWRACALNA). Następnie zostaniesz wylogowany.
94 confirm: Jesteś pewien? (tej operacji NIE MOŻNA cofnąć)
95 button: Usuń moje konto
96 reset:
97 # title: Reset area (a.k.a danger zone)
98 # description: By hiting buttons below you'll have ability to remove some informations from your account. Be aware that these actions are IRREVERSIBLE.
99 # annotations: Remove ALL annotations
100 # tags: Remove ALL tags
101 # entries: Remove ALL entries
102 # confirm: Are you really really sure? (THIS CAN'T BE UNDONE)
91 form_password: 103 form_password:
92 old_password_label: 'Stare hasło' 104 old_password_label: 'Stare hasło'
93 new_password_label: 'Nowe hasło' 105 new_password_label: 'Nowe hasło'
@@ -460,6 +472,9 @@ flashes:
460 tagging_rules_deleted: 'Reguła tagowania usunięta' 472 tagging_rules_deleted: 'Reguła tagowania usunięta'
461 user_added: 'Użytkownik "%username%" dodany' 473 user_added: 'Użytkownik "%username%" dodany'
462 rss_token_updated: 'Token kanału RSS zaktualizowany' 474 rss_token_updated: 'Token kanału RSS zaktualizowany'
475 # annotations_reset: Annotations reset
476 # tags_reset: Tags reset
477 # entries_reset: Entries reset
463 entry: 478 entry:
464 notice: 479 notice:
465 entry_already_saved: 'Wpis już został dodany %date%' 480 entry_already_saved: 'Wpis już został dodany %date%'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml
index 21f27e08..6e4813e5 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml
@@ -88,6 +88,18 @@ config:
88 name_label: 'Nume' 88 name_label: 'Nume'
89 email_label: 'E-mail' 89 email_label: 'E-mail'
90 # twoFactorAuthentication_label: 'Two factor authentication' 90 # twoFactorAuthentication_label: 'Two factor authentication'
91 delete:
92 # title: Delete my account (a.k.a danger zone)
93 # description: If you remove your account, ALL your articles, ALL your tags, ALL your annotations and your account will be PERMANENTLY removed (it can't be UNDONE). You'll then be logged out.
94 # confirm: Are you really sure? (THIS CAN'T BE UNDONE)
95 # button: Delete my account
96 reset:
97 # title: Reset area (a.k.a danger zone)
98 # description: By hiting buttons below you'll have ability to remove some informations from your account. Be aware that these actions are IRREVERSIBLE.
99 # annotations: Remove ALL annotations
100 # tags: Remove ALL tags
101 # entries: Remove ALL entries
102 # confirm: Are you really really sure? (THIS CAN'T BE UNDONE)
91 form_password: 103 form_password:
92 old_password_label: 'Parola veche' 104 old_password_label: 'Parola veche'
93 new_password_label: 'Parola nouă' 105 new_password_label: 'Parola nouă'
@@ -460,6 +472,9 @@ flashes:
460 # tagging_rules_deleted: 'Tagging rule deleted' 472 # tagging_rules_deleted: 'Tagging rule deleted'
461 # user_added: 'User "%username%" added' 473 # user_added: 'User "%username%" added'
462 # rss_token_updated: 'RSS token updated' 474 # rss_token_updated: 'RSS token updated'
475 # annotations_reset: Annotations reset
476 # tags_reset: Tags reset
477 # entries_reset: Entries reset
463 entry: 478 entry:
464 notice: 479 notice:
465 # entry_already_saved: 'Entry already saved on %date%' 480 # entry_already_saved: 'Entry already saved on %date%'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml
index f137ec99..76903102 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml
@@ -88,6 +88,18 @@ config:
88 name_label: 'İsim' 88 name_label: 'İsim'
89 email_label: 'E-posta' 89 email_label: 'E-posta'
90 twoFactorAuthentication_label: 'İki adımlı doğrulama' 90 twoFactorAuthentication_label: 'İki adımlı doğrulama'
91 delete:
92 # title: Delete my account (a.k.a danger zone)
93 # description: If you remove your account, ALL your articles, ALL your tags, ALL your annotations and your account will be PERMANENTLY removed (it can't be UNDONE). You'll then be logged out.
94 # confirm: Are you really sure? (THIS CAN'T BE UNDONE)
95 # button: Delete my account
96 reset:
97 # title: Reset area (a.k.a danger zone)
98 # description: By hiting buttons below you'll have ability to remove some informations from your account. Be aware that these actions are IRREVERSIBLE.
99 # annotations: Remove ALL annotations
100 # tags: Remove ALL tags
101 # entries: Remove ALL entries
102 # confirm: Are you really really sure? (THIS CAN'T BE UNDONE)
91 form_password: 103 form_password:
92 old_password_label: 'Eski şifre' 104 old_password_label: 'Eski şifre'
93 new_password_label: 'Yeni şifre' 105 new_password_label: 'Yeni şifre'
@@ -459,6 +471,9 @@ flashes:
459 tagging_rules_deleted: 'Tagging rule deleted' 471 tagging_rules_deleted: 'Tagging rule deleted'
460 user_added: 'User "%username%" added' 472 user_added: 'User "%username%" added'
461 rss_token_updated: 'RSS token updated' 473 rss_token_updated: 'RSS token updated'
474 # annotations_reset: Annotations reset
475 # tags_reset: Tags reset
476 # entries_reset: Entries reset
462 entry: 477 entry:
463 notice: 478 notice:
464 entry_already_saved: 'Entry already saved on %date%' 479 entry_already_saved: 'Entry already saved on %date%'
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Config/index.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Config/index.html.twig
index ff7ef73a..455d0295 100644
--- a/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Config/index.html.twig
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Config/index.html.twig
@@ -146,10 +146,41 @@
146 </fieldset> 146 </fieldset>
147 {% endif %} 147 {% endif %}
148 148
149 <h2>{{ 'config.reset.title'|trans }}</h2>
150 <fieldset class="w500p inline">
151 <p>{{ 'config.reset.description'|trans }}</p>
152 <ul>
153 <li>
154 <a href="{{ path('config_reset', { type: 'annotations'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
155 {{ 'config.reset.annotations'|trans }}
156 </a>
157 </li>
158 <li>
159 <a href="{{ path('config_reset', { type: 'tags'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
160 {{ 'config.reset.tags'|trans }}
161 </a>
162 </li>
163 <li>
164 <a href="{{ path('config_reset', { type: 'entries'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
165 {{ 'config.reset.entries'|trans }}
166 </a>
167 </li>
168 </ul>
169 </fieldset>
170
149 {{ form_widget(form.user._token) }} 171 {{ form_widget(form.user._token) }}
150 {{ form_widget(form.user.save) }} 172 {{ form_widget(form.user.save) }}
151 </form> 173 </form>
152 174
175 {% if enabled_users > 1 %}
176 <h2>{{ 'config.form_user.delete.title'|trans }}</h2>
177
178 <p>{{ 'config.form_user.delete.description'|trans }}</p>
179 <a href="{{ path('delete_account') }}" onclick="return confirm('{{ 'config.form_user.delete.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red delete-account">
180 {{ 'config.form_user.delete.button'|trans }}
181 </a>
182 {% endif %}
183
153 <h2>{{ 'config.tab_menu.password'|trans }}</h2> 184 <h2>{{ 'config.tab_menu.password'|trans }}</h2>
154 185
155 {{ form_start(form.pwd) }} 186 {{ form_start(form.pwd) }}
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/material/Config/index.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/material/Config/index.html.twig
index 19faddc0..b53ae2fe 100644
--- a/src/Wallabag/CoreBundle/Resources/views/themes/material/Config/index.html.twig
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/material/Config/index.html.twig
@@ -167,6 +167,34 @@
167 {{ form_widget(form.user.save, {'attr': {'class': 'btn waves-effect waves-light'}}) }} 167 {{ form_widget(form.user.save, {'attr': {'class': 'btn waves-effect waves-light'}}) }}
168 {{ form_widget(form.user._token) }} 168 {{ form_widget(form.user._token) }}
169 </form> 169 </form>
170
171 <br /><hr /><br />
172
173 <div class="row">
174 <h5>{{ 'config.reset.title'|trans }}</h5>
175 <p>{{ 'config.reset.description'|trans }}</p>
176 <a href="{{ path('config_reset', { type: 'annotations'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
177 {{ 'config.reset.annotations'|trans }}
178 </a>
179 <a href="{{ path('config_reset', { type: 'tags'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
180 {{ 'config.reset.tags'|trans }}
181 </a>
182 <a href="{{ path('config_reset', { type: 'entries'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
183 {{ 'config.reset.entries'|trans }}
184 </a>
185 </div>
186
187 {% if enabled_users > 1 %}
188 <br /><hr /><br />
189
190 <div class="row">
191 <h5>{{ 'config.form_user.delete.title'|trans }}</h5>
192 <p>{{ 'config.form_user.delete.description'|trans }}</p>
193 <a href="{{ path('delete_account') }}" onclick="return confirm('{{ 'config.form_user.delete.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red delete-account">
194 {{ 'config.form_user.delete.button'|trans }}
195 </a>
196 </div>
197 {% endif %}
170 </div> 198 </div>
171 199
172 <div id="set4" class="col s12"> 200 <div id="set4" class="col s12">
diff --git a/src/Wallabag/CoreBundle/Subscriber/SQLiteCascadeDeleteSubscriber.php b/src/Wallabag/CoreBundle/Subscriber/SQLiteCascadeDeleteSubscriber.php
new file mode 100644
index 00000000..f7210bd3
--- /dev/null
+++ b/src/Wallabag/CoreBundle/Subscriber/SQLiteCascadeDeleteSubscriber.php
@@ -0,0 +1,70 @@
1<?php
2
3namespace Wallabag\CoreBundle\Subscriber;
4
5use Doctrine\Common\EventSubscriber;
6use Doctrine\ORM\Event\LifecycleEventArgs;
7use Wallabag\CoreBundle\Entity\Entry;
8use Doctrine\Bundle\DoctrineBundle\Registry;
9
10/**
11 * SQLite doesn't care about cascading remove, so we need to manually remove associated stuf for an Entry.
12 * Foreign Key Support can be enabled by running `PRAGMA foreign_keys = ON;` at runtime (AT RUNTIME !).
13 * But it needs a compilation flag that not all SQLite instance has ...
14 *
15 * @see https://www.sqlite.org/foreignkeys.html#fk_enable
16 */
17class SQLiteCascadeDeleteSubscriber implements EventSubscriber
18{
19 private $doctrine;
20
21 /**
22 * @param \Doctrine\Bundle\DoctrineBundle\Registry $doctrine
23 */
24 public function __construct(Registry $doctrine)
25 {
26 $this->doctrine = $doctrine;
27 }
28
29 /**
30 * @return array
31 */
32 public function getSubscribedEvents()
33 {
34 return [
35 'preRemove',
36 ];
37 }
38
39 /**
40 * We removed everything related to the upcoming removed entry because SQLite can't handle it on it own.
41 * We do it in the preRemove, because we can't retrieve tags in the postRemove (because the entry id is gone).
42 *
43 * @param LifecycleEventArgs $args
44 */
45 public function preRemove(LifecycleEventArgs $args)
46 {
47 $entity = $args->getEntity();
48
49 if (!$this->doctrine->getConnection()->getDriver() instanceof \Doctrine\DBAL\Driver\PDOSqlite\Driver ||
50 !$entity instanceof Entry) {
51 return;
52 }
53
54 $em = $this->doctrine->getManager();
55
56 if (null !== $entity->getTags()) {
57 foreach ($entity->getTags() as $tag) {
58 $entity->removeTag($tag);
59 }
60 }
61
62 if (null !== $entity->getAnnotations()) {
63 foreach ($entity->getAnnotations() as $annotation) {
64 $em->remove($annotation);
65 }
66 }
67
68 $em->flush();
69 }
70}
diff --git a/src/Wallabag/UserBundle/Repository/UserRepository.php b/src/Wallabag/UserBundle/Repository/UserRepository.php
index 009c4881..445edb3c 100644
--- a/src/Wallabag/UserBundle/Repository/UserRepository.php
+++ b/src/Wallabag/UserBundle/Repository/UserRepository.php
@@ -38,4 +38,18 @@ class UserRepository extends EntityRepository
38 ->getQuery() 38 ->getQuery()
39 ->getSingleResult(); 39 ->getSingleResult();
40 } 40 }
41
42 /**
43 * Count how many users are enabled.
44 *
45 * @return int
46 */
47 public function getSumEnabledUsers()
48 {
49 return $this->createQueryBuilder('u')
50 ->select('count(u)')
51 ->andWhere('u.expired = false')
52 ->getQuery()
53 ->getSingleScalarResult();
54 }
41} 55}
diff --git a/src/Wallabag/UserBundle/Resources/config/services.yml b/src/Wallabag/UserBundle/Resources/config/services.yml
index eb9c8e67..8062e53f 100644
--- a/src/Wallabag/UserBundle/Resources/config/services.yml
+++ b/src/Wallabag/UserBundle/Resources/config/services.yml
@@ -21,7 +21,7 @@ services:
21 arguments: 21 arguments:
22 - WallabagUserBundle:User 22 - WallabagUserBundle:User
23 23
24 wallabag_user.create_config: 24 wallabag_user.listener.create_config:
25 class: Wallabag\UserBundle\EventListener\CreateConfigListener 25 class: Wallabag\UserBundle\EventListener\CreateConfigListener
26 arguments: 26 arguments:
27 - "@doctrine.orm.entity_manager" 27 - "@doctrine.orm.entity_manager"
diff --git a/tests/Wallabag/AnnotationBundle/Controller/AnnotationControllerTest.php b/tests/Wallabag/AnnotationBundle/Controller/AnnotationControllerTest.php
index 70849f74..cee0b847 100644
--- a/tests/Wallabag/AnnotationBundle/Controller/AnnotationControllerTest.php
+++ b/tests/Wallabag/AnnotationBundle/Controller/AnnotationControllerTest.php
@@ -3,35 +3,80 @@
3namespace Tests\AnnotationBundle\Controller; 3namespace Tests\AnnotationBundle\Controller;
4 4
5use Tests\Wallabag\AnnotationBundle\WallabagAnnotationTestCase; 5use Tests\Wallabag\AnnotationBundle\WallabagAnnotationTestCase;
6use Wallabag\AnnotationBundle\Entity\Annotation;
7use Wallabag\CoreBundle\Entity\Entry;
6 8
7class AnnotationControllerTest extends WallabagAnnotationTestCase 9class AnnotationControllerTest extends WallabagAnnotationTestCase
8{ 10{
9 public function testGetAnnotations() 11 /**
12 * This data provider allow to tests annotation from the :
13 * - API POV (when user use the api to manage annotations)
14 * - and User POV (when user use the web interface - using javascript - to manage annotations)
15 */
16 public function dataForEachAnnotations()
10 { 17 {
11 $annotation = $this->client->getContainer() 18 return [
12 ->get('doctrine.orm.entity_manager') 19 ['/api/annotations'],
13 ->getRepository('WallabagAnnotationBundle:Annotation') 20 ['annotations'],
14 ->findOneByUsername('admin'); 21 ];
22 }
23
24 /**
25 * Test fetching annotations for an entry.
26 *
27 * @dataProvider dataForEachAnnotations
28 */
29 public function testGetAnnotations($prefixUrl)
30 {
31 $em = $this->client->getContainer()->get('doctrine.orm.entity_manager');
32
33 $user = $em
34 ->getRepository('WallabagUserBundle:User')
35 ->findOneByUserName('admin');
36 $entry = $em
37 ->getRepository('WallabagCoreBundle:Entry')
38 ->findOneByUsernameAndNotArchived('admin');
15 39
16 if (!$annotation) { 40 $annotation = new Annotation($user);
17 $this->markTestSkipped('No content found in db.'); 41 $annotation->setEntry($entry);
42 $annotation->setText('This is my annotation /o/');
43 $annotation->setQuote('content');
44
45 $em->persist($annotation);
46 $em->flush();
47
48 if ('annotations' === $prefixUrl) {
49 $this->logInAs('admin');
18 } 50 }
19 51
20 $this->logInAs('admin'); 52 $this->client->request('GET', $prefixUrl.'/'.$entry->getId().'.json');
21 $crawler = $this->client->request('GET', 'annotations/'.$annotation->getEntry()->getId().'.json');
22 $this->assertEquals(200, $this->client->getResponse()->getStatusCode()); 53 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
23 54
24 $content = json_decode($this->client->getResponse()->getContent(), true); 55 $content = json_decode($this->client->getResponse()->getContent(), true);
25 $this->assertEquals(1, $content['total']); 56 $this->assertGreaterThanOrEqual(1, $content['total']);
26 $this->assertEquals($annotation->getText(), $content['rows'][0]['text']); 57 $this->assertEquals($annotation->getText(), $content['rows'][0]['text']);
58
59 // we need to re-fetch the annotation becase after the flush, it has been "detached" from the entity manager
60 $annotation = $em->getRepository('WallabagAnnotationBundle:Annotation')->findAnnotationById($annotation->getId());
61 $em->remove($annotation);
62 $em->flush();
27 } 63 }
28 64
29 public function testSetAnnotation() 65 /**
66 * Test creating an annotation for an entry.
67 *
68 * @dataProvider dataForEachAnnotations
69 */
70 public function testSetAnnotation($prefixUrl)
30 { 71 {
31 $this->logInAs('admin'); 72 $em = $this->client->getContainer()->get('doctrine.orm.entity_manager');
32 73
33 $entry = $this->client->getContainer() 74 if ('annotations' === $prefixUrl) {
34 ->get('doctrine.orm.entity_manager') 75 $this->logInAs('admin');
76 }
77
78 /** @var Entry $entry */
79 $entry = $em
35 ->getRepository('WallabagCoreBundle:Entry') 80 ->getRepository('WallabagCoreBundle:Entry')
36 ->findOneByUsernameAndNotArchived('admin'); 81 ->findOneByUsernameAndNotArchived('admin');
37 82
@@ -41,7 +86,7 @@ class AnnotationControllerTest extends WallabagAnnotationTestCase
41 'quote' => 'my quote', 86 'quote' => 'my quote',
42 'ranges' => ['start' => '', 'startOffset' => 24, 'end' => '', 'endOffset' => 31], 87 'ranges' => ['start' => '', 'startOffset' => 24, 'end' => '', 'endOffset' => 31],
43 ]); 88 ]);
44 $crawler = $this->client->request('POST', 'annotations/'.$entry->getId().'.json', [], [], $headers, $content); 89 $this->client->request('POST', $prefixUrl.'/'.$entry->getId().'.json', [], [], $headers, $content);
45 90
46 $this->assertEquals(200, $this->client->getResponse()->getStatusCode()); 91 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
47 92
@@ -52,6 +97,7 @@ class AnnotationControllerTest extends WallabagAnnotationTestCase
52 $this->assertEquals('my annotation', $content['text']); 97 $this->assertEquals('my annotation', $content['text']);
53 $this->assertEquals('my quote', $content['quote']); 98 $this->assertEquals('my quote', $content['quote']);
54 99
100 /** @var Annotation $annotation */
55 $annotation = $this->client->getContainer() 101 $annotation = $this->client->getContainer()
56 ->get('doctrine.orm.entity_manager') 102 ->get('doctrine.orm.entity_manager')
57 ->getRepository('WallabagAnnotationBundle:Annotation') 103 ->getRepository('WallabagAnnotationBundle:Annotation')
@@ -60,20 +106,35 @@ class AnnotationControllerTest extends WallabagAnnotationTestCase
60 $this->assertEquals('my annotation', $annotation->getText()); 106 $this->assertEquals('my annotation', $annotation->getText());
61 } 107 }
62 108
63 public function testEditAnnotation() 109 /**
110 * Test editing an existing annotation.
111 *
112 * @dataProvider dataForEachAnnotations
113 */
114 public function testEditAnnotation($prefixUrl)
64 { 115 {
65 $annotation = $this->client->getContainer() 116 $em = $this->client->getContainer()->get('doctrine.orm.entity_manager');
66 ->get('doctrine.orm.entity_manager')
67 ->getRepository('WallabagAnnotationBundle:Annotation')
68 ->findOneByUsername('admin');
69 117
70 $this->logInAs('admin'); 118 $user = $em
119 ->getRepository('WallabagUserBundle:User')
120 ->findOneByUserName('admin');
121 $entry = $em
122 ->getRepository('WallabagCoreBundle:Entry')
123 ->findOneByUsernameAndNotArchived('admin');
124
125 $annotation = new Annotation($user);
126 $annotation->setEntry($entry);
127 $annotation->setText('This is my annotation /o/');
128 $annotation->setQuote('my quote');
129
130 $em->persist($annotation);
131 $em->flush();
71 132
72 $headers = ['CONTENT_TYPE' => 'application/json']; 133 $headers = ['CONTENT_TYPE' => 'application/json'];
73 $content = json_encode([ 134 $content = json_encode([
74 'text' => 'a modified annotation', 135 'text' => 'a modified annotation',
75 ]); 136 ]);
76 $crawler = $this->client->request('PUT', 'annotations/'.$annotation->getId().'.json', [], [], $headers, $content); 137 $this->client->request('PUT', $prefixUrl.'/'.$annotation->getId().'.json', [], [], $headers, $content);
77 $this->assertEquals(200, $this->client->getResponse()->getStatusCode()); 138 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
78 139
79 $content = json_decode($this->client->getResponse()->getContent(), true); 140 $content = json_decode($this->client->getResponse()->getContent(), true);
@@ -83,35 +144,56 @@ class AnnotationControllerTest extends WallabagAnnotationTestCase
83 $this->assertEquals('a modified annotation', $content['text']); 144 $this->assertEquals('a modified annotation', $content['text']);
84 $this->assertEquals('my quote', $content['quote']); 145 $this->assertEquals('my quote', $content['quote']);
85 146
86 $annotationUpdated = $this->client->getContainer() 147 /** @var Annotation $annotationUpdated */
87 ->get('doctrine.orm.entity_manager') 148 $annotationUpdated = $em
88 ->getRepository('WallabagAnnotationBundle:Annotation') 149 ->getRepository('WallabagAnnotationBundle:Annotation')
89 ->findOneById($annotation->getId()); 150 ->findOneById($annotation->getId());
90 $this->assertEquals('a modified annotation', $annotationUpdated->getText()); 151 $this->assertEquals('a modified annotation', $annotationUpdated->getText());
152
153 $em->remove($annotationUpdated);
154 $em->flush();
91 } 155 }
92 156
93 public function testDeleteAnnotation() 157 /**
158 * Test deleting an annotation.
159 *
160 * @dataProvider dataForEachAnnotations
161 */
162 public function testDeleteAnnotation($prefixUrl)
94 { 163 {
95 $annotation = $this->client->getContainer() 164 $em = $this->client->getContainer()->get('doctrine.orm.entity_manager');
96 ->get('doctrine.orm.entity_manager')
97 ->getRepository('WallabagAnnotationBundle:Annotation')
98 ->findOneByUsername('admin');
99 165
100 $this->logInAs('admin'); 166 $user = $em
167 ->getRepository('WallabagUserBundle:User')
168 ->findOneByUserName('admin');
169 $entry = $em
170 ->getRepository('WallabagCoreBundle:Entry')
171 ->findOneByUsernameAndNotArchived('admin');
172
173 $annotation = new Annotation($user);
174 $annotation->setEntry($entry);
175 $annotation->setText('This is my annotation /o/');
176 $annotation->setQuote('my quote');
177
178 $em->persist($annotation);
179 $em->flush();
180
181 if ('annotations' === $prefixUrl) {
182 $this->logInAs('admin');
183 }
101 184
102 $headers = ['CONTENT_TYPE' => 'application/json']; 185 $headers = ['CONTENT_TYPE' => 'application/json'];
103 $content = json_encode([ 186 $content = json_encode([
104 'text' => 'a modified annotation', 187 'text' => 'a modified annotation',
105 ]); 188 ]);
106 $crawler = $this->client->request('DELETE', 'annotations/'.$annotation->getId().'.json', [], [], $headers, $content); 189 $this->client->request('DELETE', $prefixUrl.'/'.$annotation->getId().'.json', [], [], $headers, $content);
107 $this->assertEquals(200, $this->client->getResponse()->getStatusCode()); 190 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
108 191
109 $content = json_decode($this->client->getResponse()->getContent(), true); 192 $content = json_decode($this->client->getResponse()->getContent(), true);
110 193
111 $this->assertEquals('a modified annotation', $content['text']); 194 $this->assertEquals('This is my annotation /o/', $content['text']);
112 195
113 $annotationDeleted = $this->client->getContainer() 196 $annotationDeleted = $em
114 ->get('doctrine.orm.entity_manager')
115 ->getRepository('WallabagAnnotationBundle:Annotation') 197 ->getRepository('WallabagAnnotationBundle:Annotation')
116 ->findOneById($annotation->getId()); 198 ->findOneById($annotation->getId());
117 199
diff --git a/tests/Wallabag/AnnotationBundle/WallabagAnnotationTestCase.php b/tests/Wallabag/AnnotationBundle/WallabagAnnotationTestCase.php
index 82790a5c..ef3f1324 100644
--- a/tests/Wallabag/AnnotationBundle/WallabagAnnotationTestCase.php
+++ b/tests/Wallabag/AnnotationBundle/WallabagAnnotationTestCase.php
@@ -8,7 +8,7 @@ use Symfony\Component\BrowserKit\Cookie;
8abstract class WallabagAnnotationTestCase extends WebTestCase 8abstract class WallabagAnnotationTestCase extends WebTestCase
9{ 9{
10 /** 10 /**
11 * @var Client 11 * @var \Symfony\Bundle\FrameworkBundle\Client
12 */ 12 */
13 protected $client = null; 13 protected $client = null;
14 14
@@ -35,7 +35,7 @@ abstract class WallabagAnnotationTestCase extends WebTestCase
35 } 35 }
36 36
37 /** 37 /**
38 * @return Client 38 * @return \Symfony\Bundle\FrameworkBundle\Client
39 */ 39 */
40 protected function createAuthorizedClient() 40 protected function createAuthorizedClient()
41 { 41 {
@@ -49,7 +49,7 @@ abstract class WallabagAnnotationTestCase extends WebTestCase
49 $firewallName = $container->getParameter('fos_user.firewall_name'); 49 $firewallName = $container->getParameter('fos_user.firewall_name');
50 50
51 $this->user = $userManager->findUserBy(['username' => 'admin']); 51 $this->user = $userManager->findUserBy(['username' => 'admin']);
52 $loginManager->loginUser($firewallName, $this->user); 52 $loginManager->logInUser($firewallName, $this->user);
53 53
54 // save the login token into the session and put it in a cookie 54 // save the login token into the session and put it in a cookie
55 $container->get('session')->set('_security_'.$firewallName, serialize($container->get('security.token_storage')->getToken())); 55 $container->get('session')->set('_security_'.$firewallName, serialize($container->get('security.token_storage')->getToken()));
diff --git a/tests/Wallabag/ApiBundle/Controller/WallabagRestControllerTest.php b/tests/Wallabag/ApiBundle/Controller/WallabagRestControllerTest.php
index 5dcb3e00..6bca3c8b 100644
--- a/tests/Wallabag/ApiBundle/Controller/WallabagRestControllerTest.php
+++ b/tests/Wallabag/ApiBundle/Controller/WallabagRestControllerTest.php
@@ -32,12 +32,55 @@ class WallabagRestControllerTest extends WallabagApiTestCase
32 $this->assertEquals($entry->getUserEmail(), $content['user_email']); 32 $this->assertEquals($entry->getUserEmail(), $content['user_email']);
33 $this->assertEquals($entry->getUserId(), $content['user_id']); 33 $this->assertEquals($entry->getUserId(), $content['user_id']);
34 34
35 $this->assertTrue( 35 $this->assertEquals('application/json', $this->client->getResponse()->headers->get('Content-Type'));
36 $this->client->getResponse()->headers->contains( 36 }
37 'Content-Type', 37
38 'application/json' 38 public function testExportEntry()
39 ) 39 {
40 ); 40 $entry = $this->client->getContainer()
41 ->get('doctrine.orm.entity_manager')
42 ->getRepository('WallabagCoreBundle:Entry')
43 ->findOneBy(['user' => 1, 'isArchived' => false]);
44
45 if (!$entry) {
46 $this->markTestSkipped('No content found in db.');
47 }
48
49 $this->client->request('GET', '/api/entries/'.$entry->getId().'/export.epub');
50 $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
51
52 // epub format got the content type in the content
53 $this->assertContains('application/epub', $this->client->getResponse()->getContent());
54 $this->assertEquals('application/epub+zip', $this->client->getResponse()->headers->get('Content-Type'));
55
56 // re-auth client for mobi
57 $client = $this->createAuthorizedClient();
58 $client->request('GET', '/api/entries/'.$entry->getId().'/export.mobi');
59 $this->assertEquals(200, $client->getResponse()->getStatusCode());
60
61 $this->assertEquals('application/x-mobipocket-ebook', $client->getResponse()->headers->get('Content-Type'));
62
63 // re-auth client for pdf
64 $client = $this->createAuthorizedClient();
65 $client->request('GET', '/api/entries/'.$entry->getId().'/export.pdf');
66 $this->assertEquals(200, $client->getResponse()->getStatusCode());
67
68 $this->assertContains('PDF-', $client->getResponse()->getContent());
69 $this->assertEquals('application/pdf', $client->getResponse()->headers->get('Content-Type'));
70
71 // re-auth client for pdf
72 $client = $this->createAuthorizedClient();
73 $client->request('GET', '/api/entries/'.$entry->getId().'/export.txt');
74 $this->assertEquals(200, $client->getResponse()->getStatusCode());
75
76 $this->assertContains('text/plain', $client->getResponse()->headers->get('Content-Type'));
77
78 // re-auth client for pdf
79 $client = $this->createAuthorizedClient();
80 $client->request('GET', '/api/entries/'.$entry->getId().'/export.csv');
81 $this->assertEquals(200, $client->getResponse()->getStatusCode());
82
83 $this->assertContains('application/csv', $client->getResponse()->headers->get('Content-Type'));
41 } 84 }
42 85
43 public function testGetOneEntryWrongUser() 86 public function testGetOneEntryWrongUser()
@@ -70,12 +113,7 @@ class WallabagRestControllerTest extends WallabagApiTestCase
70 $this->assertEquals(1, $content['page']); 113 $this->assertEquals(1, $content['page']);
71 $this->assertGreaterThanOrEqual(1, $content['pages']); 114 $this->assertGreaterThanOrEqual(1, $content['pages']);
72 115
73 $this->assertTrue( 116 $this->assertEquals('application/json', $this->client->getResponse()->headers->get('Content-Type'));
74 $this->client->getResponse()->headers->contains(
75 'Content-Type',
76 'application/json'
77 )
78 );
79 } 117 }
80 118
81 public function testGetEntriesWithFullOptions() 119 public function testGetEntriesWithFullOptions()
@@ -117,12 +155,7 @@ class WallabagRestControllerTest extends WallabagApiTestCase
117 $this->assertContains('since=1443274283', $content['_links'][$link]['href']); 155 $this->assertContains('since=1443274283', $content['_links'][$link]['href']);
118 } 156 }
119 157
120 $this->assertTrue( 158 $this->assertEquals('application/json', $this->client->getResponse()->headers->get('Content-Type'));
121 $this->client->getResponse()->headers->contains(
122 'Content-Type',
123 'application/json'
124 )
125 );
126 } 159 }
127 160
128 public function testGetStarredEntries() 161 public function testGetStarredEntries()
@@ -150,12 +183,7 @@ class WallabagRestControllerTest extends WallabagApiTestCase
150 $this->assertContains('sort=updated', $content['_links'][$link]['href']); 183 $this->assertContains('sort=updated', $content['_links'][$link]['href']);
151 } 184 }
152 185
153 $this->assertTrue( 186 $this->assertEquals('application/json', $this->client->getResponse()->headers->get('Content-Type'));
154 $this->client->getResponse()->headers->contains(
155 'Content-Type',
156 'application/json'
157 )
158 );
159 } 187 }
160 188
161 public function testGetArchiveEntries() 189 public function testGetArchiveEntries()
@@ -182,12 +210,7 @@ class WallabagRestControllerTest extends WallabagApiTestCase
182 $this->assertContains('archive=1', $content['_links'][$link]['href']); 210 $this->assertContains('archive=1', $content['_links'][$link]['href']);
183 } 211 }
184 212
185 $this->assertTrue( 213 $this->assertEquals('application/json', $this->client->getResponse()->headers->get('Content-Type'));
186 $this->client->getResponse()->headers->contains(
187 'Content-Type',
188 'application/json'
189 )
190 );
191 } 214 }
192 215
193 public function testGetTaggedEntries() 216 public function testGetTaggedEntries()
@@ -214,12 +237,7 @@ class WallabagRestControllerTest extends WallabagApiTestCase
214 $this->assertContains('tags='.urlencode('foo,bar'), $content['_links'][$link]['href']); 237 $this->assertContains('tags='.urlencode('foo,bar'), $content['_links'][$link]['href']);
215 } 238 }
216 239
217 $this->assertTrue( 240 $this->assertEquals('application/json', $this->client->getResponse()->headers->get('Content-Type'));
218 $this->client->getResponse()->headers->contains(
219 'Content-Type',
220 'application/json'
221 )
222 );
223 } 241 }
224 242
225 public function testGetDatedEntries() 243 public function testGetDatedEntries()
@@ -246,12 +264,7 @@ class WallabagRestControllerTest extends WallabagApiTestCase
246 $this->assertContains('since=1443274283', $content['_links'][$link]['href']); 264 $this->assertContains('since=1443274283', $content['_links'][$link]['href']);
247 } 265 }
248 266
249 $this->assertTrue( 267 $this->assertEquals('application/json', $this->client->getResponse()->headers->get('Content-Type'));
250 $this->client->getResponse()->headers->contains(
251 'Content-Type',
252 'application/json'
253 )
254 );
255 } 268 }
256 269
257 public function testGetDatedSupEntries() 270 public function testGetDatedSupEntries()
@@ -279,12 +292,7 @@ class WallabagRestControllerTest extends WallabagApiTestCase
279 $this->assertContains('since='.($future->getTimestamp() + 1000), $content['_links'][$link]['href']); 292 $this->assertContains('since='.($future->getTimestamp() + 1000), $content['_links'][$link]['href']);
280 } 293 }
281 294
282 $this->assertTrue( 295 $this->assertEquals('application/json', $this->client->getResponse()->headers->get('Content-Type'));
283 $this->client->getResponse()->headers->contains(
284 'Content-Type',
285 'application/json'
286 )
287 );
288 } 296 }
289 297
290 public function testDeleteEntry() 298 public function testDeleteEntry()
diff --git a/tests/Wallabag/CoreBundle/Controller/ConfigControllerTest.php b/tests/Wallabag/CoreBundle/Controller/ConfigControllerTest.php
index 1954c654..8d0644d1 100644
--- a/tests/Wallabag/CoreBundle/Controller/ConfigControllerTest.php
+++ b/tests/Wallabag/CoreBundle/Controller/ConfigControllerTest.php
@@ -3,6 +3,11 @@
3namespace Tests\Wallabag\CoreBundle\Controller; 3namespace Tests\Wallabag\CoreBundle\Controller;
4 4
5use Tests\Wallabag\CoreBundle\WallabagCoreTestCase; 5use Tests\Wallabag\CoreBundle\WallabagCoreTestCase;
6use Wallabag\CoreBundle\Entity\Config;
7use Wallabag\UserBundle\Entity\User;
8use Wallabag\CoreBundle\Entity\Entry;
9use Wallabag\CoreBundle\Entity\Tag;
10use Wallabag\AnnotationBundle\Entity\Annotation;
6 11
7class ConfigControllerTest extends WallabagCoreTestCase 12class ConfigControllerTest extends WallabagCoreTestCase
8{ 13{
@@ -570,4 +575,264 @@ class ConfigControllerTest extends WallabagCoreTestCase
570 $config->set('demo_mode_enabled', 0); 575 $config->set('demo_mode_enabled', 0);
571 $config->set('demo_mode_username', 'wallabag'); 576 $config->set('demo_mode_username', 'wallabag');
572 } 577 }
578
579 public function testDeleteUserButtonVisibility()
580 {
581 $this->logInAs('admin');
582 $client = $this->getClient();
583
584 $crawler = $client->request('GET', '/config');
585
586 $this->assertGreaterThan(1, $body = $crawler->filter('body')->extract(['_text']));
587 $this->assertContains('config.form_user.delete.button', $body[0]);
588
589 $em = $client->getContainer()->get('doctrine.orm.entity_manager');
590
591 $user = $em
592 ->getRepository('WallabagUserBundle:User')
593 ->findOneByUsername('empty');
594 $user->setExpired(1);
595 $em->persist($user);
596
597 $user = $em
598 ->getRepository('WallabagUserBundle:User')
599 ->findOneByUsername('bob');
600 $user->setExpired(1);
601 $em->persist($user);
602
603 $em->flush();
604
605 $crawler = $client->request('GET', '/config');
606
607 $this->assertGreaterThan(1, $body = $crawler->filter('body')->extract(['_text']));
608 $this->assertNotContains('config.form_user.delete.button', $body[0]);
609
610 $client->request('GET', '/account/delete');
611 $this->assertEquals(403, $client->getResponse()->getStatusCode());
612
613 $user = $em
614 ->getRepository('WallabagUserBundle:User')
615 ->findOneByUsername('empty');
616 $user->setExpired(0);
617 $em->persist($user);
618
619 $user = $em
620 ->getRepository('WallabagUserBundle:User')
621 ->findOneByUsername('bob');
622 $user->setExpired(0);
623 $em->persist($user);
624
625 $em->flush();
626 }
627
628 public function testDeleteAccount()
629 {
630 $client = $this->getClient();
631 $em = $client->getContainer()->get('doctrine.orm.entity_manager');
632
633 $user = new User();
634 $user->setName('Wallace');
635 $user->setEmail('wallace@wallabag.org');
636 $user->setUsername('wallace');
637 $user->setPlainPassword('wallace');
638 $user->setEnabled(true);
639 $user->addRole('ROLE_SUPER_ADMIN');
640
641 $em->persist($user);
642
643 $config = new Config($user);
644
645 $config->setTheme('material');
646 $config->setItemsPerPage(30);
647 $config->setReadingSpeed(1);
648 $config->setLanguage('en');
649 $config->setPocketConsumerKey('xxxxx');
650
651 $em->persist($config);
652 $em->flush();
653
654 $this->logInAs('wallace');
655 $loggedInUserId = $this->getLoggedInUserId();
656
657 // create entry to check after user deletion
658 // that this entry is also deleted
659 $crawler = $client->request('GET', '/new');
660
661 $this->assertEquals(200, $client->getResponse()->getStatusCode());
662
663 $form = $crawler->filter('form[name=entry]')->form();
664 $data = [
665 'entry[url]' => $url = 'https://github.com/wallabag/wallabag',
666 ];
667
668 $client->submit($form, $data);
669 $this->assertEquals(302, $client->getResponse()->getStatusCode());
670
671 $crawler = $client->request('GET', '/config');
672
673 $deleteLink = $crawler->filter('.delete-account')->last()->link();
674
675 $client->click($deleteLink);
676 $this->assertEquals(302, $client->getResponse()->getStatusCode());
677
678 $em = $client->getContainer()->get('doctrine.orm.entity_manager');
679 $user = $em
680 ->getRepository('WallabagUserBundle:User')
681 ->createQueryBuilder('u')
682 ->where('u.username = :username')->setParameter('username', 'wallace')
683 ->getQuery()
684 ->getOneOrNullResult()
685 ;
686
687 $this->assertNull($user);
688
689 $entries = $client->getContainer()
690 ->get('doctrine.orm.entity_manager')
691 ->getRepository('WallabagCoreBundle:Entry')
692 ->findByUser($loggedInUserId);
693
694 $this->assertEmpty($entries);
695 }
696
697 public function testReset()
698 {
699 $this->logInAs('empty');
700 $client = $this->getClient();
701
702 $em = $client->getContainer()->get('doctrine.orm.entity_manager');
703
704 $user = static::$kernel->getContainer()->get('security.token_storage')->getToken()->getUser();
705
706 $tag = new Tag();
707 $tag->setLabel('super');
708 $em->persist($tag);
709
710 $entry = new Entry($user);
711 $entry->setUrl('http://www.lemonde.fr/europe/article/2016/10/01/pour-le-psoe-chaque-election-s-est-transformee-en-une-agonie_5006476_3214.html');
712 $entry->setContent('Youhou');
713 $entry->setTitle('Youhou');
714 $entry->addTag($tag);
715 $em->persist($entry);
716
717 $entry2 = new Entry($user);
718 $entry2->setUrl('http://www.lemonde.de/europe/article/2016/10/01/pour-le-psoe-chaque-election-s-est-transformee-en-une-agonie_5006476_3214.html');
719 $entry2->setContent('Youhou');
720 $entry2->setTitle('Youhou');
721 $entry2->addTag($tag);
722 $em->persist($entry2);
723
724 $annotation = new Annotation($user);
725 $annotation->setText('annotated');
726 $annotation->setQuote('annotated');
727 $annotation->setRanges([]);
728 $annotation->setEntry($entry);
729 $em->persist($annotation);
730
731 $em->flush();
732
733 // reset annotations
734 $crawler = $client->request('GET', '/config#set3');
735
736 $this->assertEquals(200, $client->getResponse()->getStatusCode());
737
738 $crawler = $client->click($crawler->selectLink('config.reset.annotations')->link());
739
740 $this->assertEquals(302, $client->getResponse()->getStatusCode());
741 $this->assertContains('flashes.config.notice.annotations_reset', $client->getContainer()->get('session')->getFlashBag()->get('notice')[0]);
742
743 $annotationsReset = $em
744 ->getRepository('WallabagAnnotationBundle:Annotation')
745 ->findAnnotationsByPageId($entry->getId(), $user->getId());
746
747 $this->assertEmpty($annotationsReset, 'Annotations were reset');
748
749 // reset tags
750 $crawler = $client->request('GET', '/config#set3');
751
752 $this->assertEquals(200, $client->getResponse()->getStatusCode());
753
754 $crawler = $client->click($crawler->selectLink('config.reset.tags')->link());
755
756 $this->assertEquals(302, $client->getResponse()->getStatusCode());
757 $this->assertContains('flashes.config.notice.tags_reset', $client->getContainer()->get('session')->getFlashBag()->get('notice')[0]);
758
759 $tagReset = $em
760 ->getRepository('WallabagCoreBundle:Tag')
761 ->countAllTags($user->getId());
762
763 $this->assertEquals(0, $tagReset, 'Tags were reset');
764
765 // reset entries
766 $crawler = $client->request('GET', '/config#set3');
767
768 $this->assertEquals(200, $client->getResponse()->getStatusCode());
769
770 $crawler = $client->click($crawler->selectLink('config.reset.entries')->link());
771
772 $this->assertEquals(302, $client->getResponse()->getStatusCode());
773 $this->assertContains('flashes.config.notice.entries_reset', $client->getContainer()->get('session')->getFlashBag()->get('notice')[0]);
774
775 $entryReset = $em
776 ->getRepository('WallabagCoreBundle:Entry')
777 ->countAllEntriesByUsername($user->getId());
778
779 $this->assertEquals(0, $entryReset, 'Entries were reset');
780 }
781
782 public function testResetEntriesCascade()
783 {
784 $this->logInAs('empty');
785 $client = $this->getClient();
786
787 $em = $client->getContainer()->get('doctrine.orm.entity_manager');
788
789 $user = static::$kernel->getContainer()->get('security.token_storage')->getToken()->getUser();
790
791 $tag = new Tag();
792 $tag->setLabel('super');
793 $em->persist($tag);
794
795 $entry = new Entry($user);
796 $entry->setUrl('http://www.lemonde.fr/europe/article/2016/10/01/pour-le-psoe-chaque-election-s-est-transformee-en-une-agonie_5006476_3214.html');
797 $entry->setContent('Youhou');
798 $entry->setTitle('Youhou');
799 $entry->addTag($tag);
800 $em->persist($entry);
801
802 $annotation = new Annotation($user);
803 $annotation->setText('annotated');
804 $annotation->setQuote('annotated');
805 $annotation->setRanges([]);
806 $annotation->setEntry($entry);
807 $em->persist($annotation);
808
809 $em->flush();
810
811 $crawler = $client->request('GET', '/config#set3');
812
813 $this->assertEquals(200, $client->getResponse()->getStatusCode());
814
815 $crawler = $client->click($crawler->selectLink('config.reset.entries')->link());
816
817 $this->assertEquals(302, $client->getResponse()->getStatusCode());
818 $this->assertContains('flashes.config.notice.entries_reset', $client->getContainer()->get('session')->getFlashBag()->get('notice')[0]);
819
820 $entryReset = $em
821 ->getRepository('WallabagCoreBundle:Entry')
822 ->countAllEntriesByUsername($user->getId());
823
824 $this->assertEquals(0, $entryReset, 'Entries were reset');
825
826 $tagReset = $em
827 ->getRepository('WallabagCoreBundle:Tag')
828 ->countAllTags($user->getId());
829
830 $this->assertEquals(0, $tagReset, 'Tags were reset');
831
832 $annotationsReset = $em
833 ->getRepository('WallabagAnnotationBundle:Annotation')
834 ->findAnnotationsByPageId($entry->getId(), $user->getId());
835
836 $this->assertEmpty($annotationsReset, 'Annotations were reset');
837 }
573} 838}