]> git.immae.eu Git - github/wallabag/wallabag.git/commitdiff
Merge pull request #2401 from wallabag/reset-account
authorJeremy Benoist <j0k3r@users.noreply.github.com>
Mon, 24 Oct 2016 09:57:51 +0000 (11:57 +0200)
committerGitHub <noreply@github.com>
Mon, 24 Oct 2016 09:57:51 +0000 (11:57 +0200)
Reset account

39 files changed:
app/DoctrineMigrations/Version20160410190541.php
app/DoctrineMigrations/Version20160812120952.php
app/DoctrineMigrations/Version20160911214952.php
app/DoctrineMigrations/Version20160916201049.php
app/DoctrineMigrations/Version20161001072726.php [new file with mode: 0644]
app/DoctrineMigrations/Version20161022134138.php [new file with mode: 0644]
app/config/config.yml
app/config/config_test.yml
app/config/parameters.yml.dist
app/config/parameters_test.yml
app/config/tests/parameters_test.mysql.yml
app/config/tests/parameters_test.pgsql.yml
app/config/tests/parameters_test.sqlite.yml
docs/en/user/parameters.rst
src/Wallabag/AnnotationBundle/Entity/Annotation.php
src/Wallabag/AnnotationBundle/Repository/AnnotationRepository.php
src/Wallabag/CoreBundle/Command/InstallCommand.php
src/Wallabag/CoreBundle/Controller/ConfigController.php
src/Wallabag/CoreBundle/Controller/TagController.php
src/Wallabag/CoreBundle/Entity/Entry.php
src/Wallabag/CoreBundle/Repository/EntryRepository.php
src/Wallabag/CoreBundle/Repository/TagRepository.php
src/Wallabag/CoreBundle/Resources/config/services.yml
src/Wallabag/CoreBundle/Resources/translations/messages.da.yml
src/Wallabag/CoreBundle/Resources/translations/messages.de.yml
src/Wallabag/CoreBundle/Resources/translations/messages.en.yml
src/Wallabag/CoreBundle/Resources/translations/messages.es.yml
src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml
src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml
src/Wallabag/CoreBundle/Resources/translations/messages.it.yml
src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml
src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml
src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml
src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml
src/Wallabag/CoreBundle/Resources/views/themes/baggy/Config/index.html.twig
src/Wallabag/CoreBundle/Resources/views/themes/material/Config/index.html.twig
src/Wallabag/CoreBundle/Subscriber/SQLiteCascadeDeleteSubscriber.php [new file with mode: 0644]
src/Wallabag/UserBundle/Resources/config/services.yml
tests/Wallabag/CoreBundle/Controller/ConfigControllerTest.php

index 4014857badc802fe30f0ff57740ccd471252fd93..c41b04657ccaace8c406bf9a6934b47484cdb2f9 100644 (file)
@@ -29,8 +29,12 @@ class Version20160410190541 extends AbstractMigration implements ContainerAwareI
      */
     public function up(Schema $schema)
     {
-        $this->addSql('ALTER TABLE `'.$this->getTable('entry').'` ADD `uuid` LONGTEXT DEFAULT NULL');
-        $this->addSql("INSERT INTO `".$this->getTable('craue_config_setting')."` (`name`, `value`, `section`) VALUES ('share_public', '1', 'entry')");
+        if ($this->connection->getDatabasePlatform()->getName() == 'postgresql') {
+            $this->addSql('ALTER TABLE '.$this->getTable('entry').' ADD uuid UUID DEFAULT NULL');
+        } else {
+            $this->addSql('ALTER TABLE '.$this->getTable('entry').' ADD uuid LONGTEXT DEFAULT NULL');
+        }
+        $this->addSql("INSERT INTO ".$this->getTable('craue_config_setting')." (name, value, section) VALUES ('share_public', '1', 'entry')");
     }
 
     /**
@@ -40,7 +44,7 @@ class Version20160410190541 extends AbstractMigration implements ContainerAwareI
     {
         $this->abortIf($this->connection->getDatabasePlatform()->getName() != 'sqlite', 'This down migration can\'t be executed on SQLite databases, because SQLite don\'t support DROP COLUMN.');
 
-        $this->addSql('ALTER TABLE `'.$this->getTable('entry').'` DROP `uuid`');
-        $this->addSql("DELETE FROM `".$this->getTable('craue_config_setting')."` WHERE `name` = 'share_public'");
+        $this->addSql('ALTER TABLE '.$this->getTable('entry').' DROP uuid');
+        $this->addSql("DELETE FROM ".$this->getTable('craue_config_setting')." WHERE name = 'share_public'");
     }
 }
index a8d3bcf25a9382852a9d90e69c330b0b2eb4df71..39423e2f56d1a9eb8004bc01bb82f373435bb8c2 100644 (file)
@@ -29,10 +29,17 @@ class Version20160812120952 extends AbstractMigration implements ContainerAwareI
      */
     public function up(Schema $schema)
     {
-        if ($this->connection->getDatabasePlatform()->getName() == 'sqlite') {
-            $this->addSql('ALTER TABLE '.$this->getTable('oauth2_clients').' ADD name longtext DEFAULT NULL');
-        } else {
-            $this->addSql('ALTER TABLE '.$this->getTable('oauth2_clients').' ADD name longtext COLLATE \'utf8_unicode_ci\' DEFAULT NULL');
+        switch ($this->connection->getDatabasePlatform()->getName()) {
+            case 'sqlite':
+                $this->addSql('ALTER TABLE '.$this->getTable('oauth2_clients').' ADD name longtext DEFAULT NULL');
+                break;
+
+            case 'mysql':
+                $this->addSql('ALTER TABLE '.$this->getTable('oauth2_clients').' ADD name longtext COLLATE \'utf8_unicode_ci\' DEFAULT NULL');
+                break;
+
+            case 'postgresql':
+                $this->addSql('ALTER TABLE '.$this->getTable('oauth2_clients').' ADD name text DEFAULT NULL');
         }
     }
 
index 35809cec6e76c3f9c0e1159fc37a72b07444b423..3f988ccf8105be2f53da1976ece12e787ec27885 100644 (file)
@@ -29,8 +29,8 @@ class Version20160911214952 extends AbstractMigration implements ContainerAwareI
      */
     public function up(Schema $schema)
     {
-        $this->addSql('INSERT INTO `'.$this->getTable('craue_config_setting').'` (`name`, `value`, `section`) VALUES (\'import_with_redis\', \'0\', \'import\')');
-        $this->addSql('INSERT INTO `'.$this->getTable('craue_config_setting').'` (`name`, `value`, `section`) VALUES (\'import_with_rabbitmq\', \'0\', \'import\')');
+        $this->addSql('INSERT INTO '.$this->getTable('craue_config_setting').' (name, value, section) VALUES (\'import_with_redis\', \'0\', \'import\')');
+        $this->addSql('INSERT INTO '.$this->getTable('craue_config_setting').' (name, value, section) VALUES (\'import_with_rabbitmq\', \'0\', \'import\')');
     }
 
     /**
index 202901e69ecef4e288ff64bf0d5c210d9b728f8b..fc4e700a2cbe1f26ff24866a52df90c65e942723 100644 (file)
@@ -30,7 +30,7 @@ class Version20160916201049 extends AbstractMigration implements ContainerAwareI
     public function up(Schema $schema)
     {
         $this->addSql('ALTER TABLE '.$this->getTable('config').' ADD pocket_consumer_key VARCHAR(255) DEFAULT NULL');
-        $this->addSql("DELETE FROM `".$this->getTable('craue_config_setting')."` WHERE `name` = 'pocket_consumer_key';");
+        $this->addSql("DELETE FROM ".$this->getTable('craue_config_setting')." WHERE name = 'pocket_consumer_key';");
     }
 
     /**
@@ -40,7 +40,7 @@ class Version20160916201049 extends AbstractMigration implements ContainerAwareI
     {
         $this->abortIf($this->connection->getDatabasePlatform()->getName() == 'sqlite', 'Migration can only be executed safely on \'mysql\' or \'postgresql\'.');
 
-        $this->addSql('ALTER TABLE `'.$this->getTable('config').'` DROP pocket_consumer_key');
-        $this->addSql("INSERT INTO `".$this->getTable('craue_config_setting')."` (`name`, `value`, `section`) VALUES ('pocket_consumer_key', NULL, 'import')");
+        $this->addSql('ALTER TABLE '.$this->getTable('config').' DROP pocket_consumer_key');
+        $this->addSql("INSERT INTO ".$this->getTable('craue_config_setting')." (name, value, section) VALUES ('pocket_consumer_key', NULL, 'import')");
     }
 }
diff --git a/app/DoctrineMigrations/Version20161001072726.php b/app/DoctrineMigrations/Version20161001072726.php
new file mode 100644 (file)
index 0000000..237db93
--- /dev/null
@@ -0,0 +1,63 @@
+<?php
+
+namespace Application\Migrations;
+
+use Doctrine\DBAL\Migrations\AbstractMigration;
+use Doctrine\DBAL\Schema\Schema;
+use Symfony\Component\DependencyInjection\ContainerAwareInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+class Version20161001072726 extends AbstractMigration implements ContainerAwareInterface
+{
+    /**
+     * @var ContainerInterface
+     */
+    private $container;
+
+    public function setContainer(ContainerInterface $container = null)
+    {
+        $this->container = $container;
+    }
+
+    private function getTable($tableName)
+    {
+        return $this->container->getParameter('database_table_prefix') . $tableName;
+    }
+
+    /**
+     * @param Schema $schema
+     */
+    public function up(Schema $schema)
+    {
+        $this->skipIf($this->connection->getDatabasePlatform()->getName() == 'sqlite', 'Migration can only be executed safely on \'mysql\' or \'postgresql\'.');
+
+        // remove all FK from entry_tag
+        $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()."'");
+        $query->execute();
+
+        foreach ($query->fetchAll() as $fk) {
+            $this->addSql('ALTER TABLE '.$this->getTable('entry_tag').' DROP FOREIGN KEY '.$fk['CONSTRAINT_NAME']);
+        }
+
+        $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');
+        $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');
+
+        // remove entry FK from annotation
+        $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()."'");
+        $query->execute();
+
+        foreach ($query->fetchAll() as $fk) {
+            $this->addSql('ALTER TABLE '.$this->getTable('annotation').' DROP FOREIGN KEY '.$fk['CONSTRAINT_NAME']);
+        }
+
+        $this->addSql('ALTER TABLE '.$this->getTable('annotation').' ADD CONSTRAINT FK_annotation_entry FOREIGN KEY (entry_id) REFERENCES '.$this->getTable('entry').' (id) ON DELETE CASCADE');
+    }
+
+    /**
+     * @param Schema $schema
+     */
+    public function down(Schema $schema)
+    {
+        throw new SkipMigrationException('Too complex ...');
+    }
+}
diff --git a/app/DoctrineMigrations/Version20161022134138.php b/app/DoctrineMigrations/Version20161022134138.php
new file mode 100644 (file)
index 0000000..5cce55a
--- /dev/null
@@ -0,0 +1,77 @@
+<?php
+
+namespace Application\Migrations;
+
+use Doctrine\DBAL\Migrations\AbstractMigration;
+use Doctrine\DBAL\Schema\Schema;
+use Symfony\Component\DependencyInjection\ContainerAwareInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+class Version20161022134138 extends AbstractMigration implements ContainerAwareInterface
+{
+    /**
+     * @var ContainerInterface
+     */
+    private $container;
+
+    public function setContainer(ContainerInterface $container = null)
+    {
+        $this->container = $container;
+    }
+
+    private function getTable($tableName)
+    {
+        return $this->container->getParameter('database_table_prefix') . $tableName;
+    }
+
+    /**
+     * @param Schema $schema
+     */
+    public function up(Schema $schema)
+    {
+        $this->skipIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'This migration only apply to MySQL');
+
+        $this->addSql('ALTER DATABASE '.$this->container->getParameter('database_name').' CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;');
+
+        $this->addSql('ALTER TABLE '.$this->getTable('annotation').' CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;');
+        $this->addSql('ALTER TABLE '.$this->getTable('entry').' CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;');
+        $this->addSql('ALTER TABLE '.$this->getTable('tag').' CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;');
+        $this->addSql('ALTER TABLE '.$this->getTable('user').' CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;');
+
+        $this->addSql('ALTER TABLE '.$this->getTable('annotation').' CHANGE `text` `text` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;');
+        $this->addSql('ALTER TABLE '.$this->getTable('annotation').' CHANGE `quote` `quote` VARCHAR(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;');
+
+        $this->addSql('ALTER TABLE '.$this->getTable('entry').' CHANGE `title` `title` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;');
+        $this->addSql('ALTER TABLE '.$this->getTable('entry').' CHANGE `content` `content` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;');
+
+        $this->addSql('ALTER TABLE '.$this->getTable('tag').' CHANGE `label` `label` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;');
+
+        $this->addSql('ALTER TABLE '.$this->getTable('user').' CHANGE `name` `name` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;');
+    }
+
+    /**
+     * @param Schema $schema
+     */
+    public function down(Schema $schema)
+    {
+        $this->skipIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'This migration only apply to MySQL');
+
+        $this->addSql('ALTER DATABASE '.$this->container->getParameter('database_name').' CHARACTER SET = utf8 COLLATE = utf8_unicode_ci;');
+
+        $this->addSql('ALTER TABLE '.$this->getTable('annotation').' CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;');
+        $this->addSql('ALTER TABLE '.$this->getTable('entry').' CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;');
+        $this->addSql('ALTER TABLE '.$this->getTable('tag').' CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;');
+        $this->addSql('ALTER TABLE '.$this->getTable('user').' CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;');
+
+        $this->addSql('ALTER TABLE '.$this->getTable('annotation').' CHANGE `text` `text` longtext CHARACTER SET utf8 COLLATE utf8_unicode_ci;');
+        $this->addSql('ALTER TABLE '.$this->getTable('annotation').' CHANGE `quote` `quote` VARCHAR(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci;');
+
+        $this->addSql('ALTER TABLE '.$this->getTable('entry').' CHANGE `title` `title` longtext CHARACTER SET utf8 COLLATE utf8_unicode_ci;');
+        $this->addSql('ALTER TABLE '.$this->getTable('entry').' CHANGE `content` `content` longtext CHARACTER SET utf8 COLLATE utf8_unicode_ci;');
+
+        $this->addSql('ALTER TABLE '.$this->getTable('tag').' CHANGE `label` `label` longtext CHARACTER SET utf8 COLLATE utf8_unicode_ci;');
+
+        $this->addSql('ALTER TABLE '.$this->getTable('user').' CHANGE `name` `name` longtext CHARACTER SET utf8 COLLATE utf8_unicode_ci;');
+
+    }
+}
index b4760073b128111c9d9f815fb3fae0d516fc3aa2..9dbc9d7ccf9a0ad33998206f8144d81c6ec5894f 100644 (file)
@@ -75,7 +75,7 @@ doctrine:
         dbname: "%database_name%"
         user: "%database_user%"
         password: "%database_password%"
-        charset: UTF8
+        charset: "%database_charset%"
         path: "%database_path%"
         unix_socket: "%database_socket%"
         server_version: 5.6
index 3eab6fb2848f5afccf9c8b6b76ded8a8b845aa93..f5e2c25ead1bca8ccb1b9980fa48657f5c864c76 100644 (file)
@@ -28,7 +28,7 @@ doctrine:
         dbname: "%test_database_name%"
         user: "%test_database_user%"
         password: "%test_database_password%"
-        charset: UTF8
+        charset: "%test_database_charset%"
         path: "%test_database_path%"
     orm:
         metadata_cache_driver:
index ece4903a186ba8a3de077466fbac84a02e27b658..7a22cb9870fbe10faf13ff69062fca37a597ee61 100644 (file)
@@ -19,16 +19,18 @@ parameters:
     database_path: "%kernel.root_dir%/../data/db/wallabag.sqlite"
     database_table_prefix: wallabag_
     database_socket: null
+    # with MySQL, use "utf8mb4" if you got problem with content with emojis
+    database_charset: utf8
 
-    mailer_transport:  smtp
-    mailer_host:       127.0.0.1
-    mailer_user:       ~
-    mailer_password:   ~
+    mailer_transport: smtp
+    mailer_host: 127.0.0.1
+    mailer_user: ~
+    mailer_password: ~
 
-    locale:            en
+    locale: en
 
     # A secret key that's used to generate certain security-related tokens
-    secret:            ovmpmAWXRCabNlMgzlzFXDYmCFfzGv
+    secret: ovmpmAWXRCabNlMgzlzFXDYmCFfzGv
 
     # two factor stuff
     twofactor_auth: true
index 2943b27a75338643eac98b59b1e48d21146475ee..5f2e25bb9bb13ebe021a6e3ca6c00a7d11f51e6b 100644 (file)
@@ -6,3 +6,4 @@ parameters:
     test_database_user: null
     test_database_password: null
     test_database_path: '%kernel.root_dir%/../data/db/wallabag_test.sqlite'
+    test_database_charset: utf8
index d8512845fa6ca4f462dbe5a764ec20b7ed85d6fc..bca2d466364588c62dd13a4d2649d3708a2403d8 100644 (file)
@@ -6,3 +6,4 @@ parameters:
     test_database_user: root
     test_database_password: ~
     test_database_path: ~
+    test_database_charset: utf8mb4
index 41383868d5be06b2ffe9a931d61a9ee8cf957b61..3e18d4a0395f41fedced20f96fe9ee6241cb9e64 100644 (file)
@@ -6,3 +6,4 @@ parameters:
     test_database_user: travis
     test_database_password: ~
     test_database_path: ~
+    test_database_charset: utf8
index 1952e3a61135340aa66c461c53c53e0fbe1b3332..b8a5f41a709020ae1e59a3d47f15dd10f47732a5 100644 (file)
@@ -6,3 +6,4 @@ parameters:
     test_database_user: ~
     test_database_password: ~
     test_database_path: "%kernel.root_dir%/../data/db/wallabag_test.sqlite"
+    test_database_charset: utf8
index 79c508717d208fd8324042107a84a6cd586288fd..2fca020efbee549f1de1fccf270d44de467b4507 100644 (file)
@@ -11,7 +11,8 @@ What is the meaning of the parameters?
    "database_password", "~", "password of that user"
    "database_path", "``""%kernel.root_dir%/../data/db/wallabag.sqlite""``", "only for SQLite, define where to put the database file. Leave it for other database"
    "database_table_prefix", "wallabag_", "all wallabag's tables will be prefixed with that string. You can include a ``_`` for clarity"
-   "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"
+   "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)"
+   "database_charset", "utf8mb4", "For PostgreSQL & SQLite you should use utf8, for MySQL use utf8mb4 which handle emoji"
 
 .. csv-table:: Configuration to send emails from wallabag
    :header: "name", "default", "description"
index c48d873100e2a38a25fa37e06a04a465ea49f0c1..0838f5aa9153cb2a4c84f53fdd67779ee9828cad 100644 (file)
@@ -82,7 +82,7 @@ class Annotation
      * @Exclude
      *
      * @ORM\ManyToOne(targetEntity="Wallabag\CoreBundle\Entity\Entry", inversedBy="annotations")
-     * @ORM\JoinColumn(name="entry_id", referencedColumnName="id")
+     * @ORM\JoinColumn(name="entry_id", referencedColumnName="id", onDelete="cascade")
      */
     private $entry;
 
index 8cccffba1366bb51c46f68de2bcec354c2b3a616..8d3f07eef350f2eb8ebf12a05b80deb5f6d026fb 100644 (file)
@@ -108,4 +108,18 @@ class AnnotationRepository extends EntityRepository
             ->getQuery()
             ->getSingleResult();
     }
+
+    /**
+     * Remove all annotations for a user id.
+     * Used when a user want to reset all informations.
+     *
+     * @param int $userId
+     */
+    public function removeAllByUserId($userId)
+    {
+        $this->getEntityManager()
+            ->createQuery('DELETE FROM Wallabag\AnnotationBundle\Entity\Annotation a WHERE a.user = :userId')
+            ->setParameter('userId', $userId)
+            ->execute();
+    }
 }
index 591107821c854345f8e133fbb6d219f19b27fffa..82cd9dafaf480d430a7cd37d0cd01c31378799df 100644 (file)
@@ -40,7 +40,7 @@ class InstallCommand extends ContainerAwareCommand
     {
         $this
             ->setName('wallabag:install')
-            ->setDescription('Wallabag installer.')
+            ->setDescription('wallabag installer.')
             ->addOption(
                'reset',
                null,
@@ -55,7 +55,7 @@ class InstallCommand extends ContainerAwareCommand
         $this->defaultInput = $input;
         $this->defaultOutput = $output;
 
-        $output->writeln('<info>Installing Wallabag...</info>');
+        $output->writeln('<info>Installing wallabag...</info>');
         $output->writeln('');
 
         $this
@@ -65,7 +65,7 @@ class InstallCommand extends ContainerAwareCommand
             ->setupConfig()
         ;
 
-        $output->writeln('<info>Wallabag has been successfully installed.</info>');
+        $output->writeln('<info>wallabag has been successfully installed.</info>');
         $output->writeln('<comment>Just execute `php bin/console server:run --env=prod` for using wallabag: http://localhost:8000</comment>');
     }
 
@@ -95,7 +95,8 @@ class InstallCommand extends ContainerAwareCommand
         $help = '';
 
         try {
-            $this->getContainer()->get('doctrine')->getManager()->getConnection()->connect();
+            $conn = $this->getContainer()->get('doctrine')->getManager()->getConnection();
+            $conn->connect();
         } catch (\Exception $e) {
             if (false === strpos($e->getMessage(), 'Unknown database')
                 && false === strpos($e->getMessage(), 'database "'.$this->getContainer()->getParameter('database_name').'" does not exist')) {
@@ -107,6 +108,21 @@ class InstallCommand extends ContainerAwareCommand
 
         $rows[] = [$label, $status, $help];
 
+        // now check if MySQL isn't too old to handle utf8mb4
+        if ($conn->isConnected() && $conn->getDatabasePlatform() instanceof \Doctrine\DBAL\Platforms\MySqlPlatform) {
+            $version = $conn->query('select version()')->fetchColumn();
+            $minimalVersion = '5.5.4';
+
+            if (false === version_compare($version, $minimalVersion, '>')) {
+                $fulfilled = false;
+                $rows[] = [
+                    '<comment>Database version</comment>',
+                    '<error>ERROR!</error>',
+                    'Your MySQL version ('.$version.') is too old, consider upgrading ('.$minimalVersion.'+).',
+                ];
+            }
+        }
+
         foreach ($this->functionExists as $functionRequired) {
             $label = '<comment>'.$functionRequired.'</comment>';
             $status = '<info>OK!</info>';
@@ -131,7 +147,7 @@ class InstallCommand extends ContainerAwareCommand
             throw new \RuntimeException('Some system requirements are not fulfilled. Please check output messages and fix them.');
         }
 
-        $this->defaultOutput->writeln('<info>Success! Your system can run Wallabag properly.</info>');
+        $this->defaultOutput->writeln('<info>Success! Your system can run wallabag properly.</info>');
 
         $this->defaultOutput->writeln('');
 
index abd35c028b8aa3681e552ef62557d21eca514ad3..8d391917ad83016fc2adf2cdc63fb38fc29fc875 100644 (file)
@@ -224,6 +224,80 @@ class ConfigController extends Controller
         return $this->redirect($this->generateUrl('config').'?tagging-rule='.$rule->getId().'#set5');
     }
 
+    /**
+     * Remove all annotations OR tags OR entries for the current user.
+     *
+     * @Route("/reset/{type}", requirements={"id" = "annotations|tags|entries"}, name="config_reset")
+     *
+     * @return RedirectResponse
+     */
+    public function resetAction($type)
+    {
+        $em = $this->getDoctrine()->getManager();
+
+        switch ($type) {
+            case 'annotations':
+                $this->getDoctrine()
+                    ->getRepository('WallabagAnnotationBundle:Annotation')
+                    ->removeAllByUserId($this->getUser()->getId());
+                break;
+
+            case 'tags':
+                $this->removeAllTagsByUserId($this->getUser()->getId());
+                break;
+
+            case 'entries':
+                // SQLite doesn't care about cascading remove, so we need to manually remove associated stuf
+                // otherwise they won't be removed ...
+                if ($this->get('doctrine')->getConnection()->getDriver() instanceof \Doctrine\DBAL\Driver\PDOSqlite\Driver) {
+                    $this->getDoctrine()->getRepository('WallabagAnnotationBundle:Annotation')->removeAllByUserId($this->getUser()->getId());
+                }
+
+                // manually remove tags to avoid orphan tag
+                $this->removeAllTagsByUserId($this->getUser()->getId());
+
+                $this->getDoctrine()
+                    ->getRepository('WallabagCoreBundle:Entry')
+                    ->removeAllByUserId($this->getUser()->getId());
+        }
+
+        $this->get('session')->getFlashBag()->add(
+            'notice',
+            'flashes.config.notice.'.$type.'_reset'
+        );
+
+        return $this->redirect($this->generateUrl('config').'#set3');
+    }
+
+    /**
+     * Remove all tags for a given user and cleanup orphan tags.
+     *
+     * @param int $userId
+     */
+    private function removeAllTagsByUserId($userId)
+    {
+        $tags = $this->getDoctrine()->getRepository('WallabagCoreBundle:Tag')->findAllTags($userId);
+
+        if (empty($tags)) {
+            return;
+        }
+
+        $this->getDoctrine()
+            ->getRepository('WallabagCoreBundle:Entry')
+            ->removeTags($userId, $tags);
+
+        // cleanup orphan tags
+        $em = $this->getDoctrine()->getManager();
+
+        foreach ($tags as $tag) {
+            if (count($tag->getEntries()) === 0) {
+                $em->remove($tag);
+            }
+        }
+
+        $em->flush();
+    }
+
     /**
      * Validate that a rule can be edited/deleted by the current user.
      *
index 5acc685281a205a57cf88b92e72125cc040abc0b..4542d484c3a2a9e9aaf4b003109e0d068860c2ec 100644 (file)
@@ -90,15 +90,15 @@ class TagController extends Controller
 
         $flatTags = [];
 
-        foreach ($tags as $key => $tag) {
+        foreach ($tags as $tag) {
             $nbEntries = $this->getDoctrine()
                 ->getRepository('WallabagCoreBundle:Entry')
-                ->countAllEntriesByUserIdAndTagId($this->getUser()->getId(), $tag['id']);
+                ->countAllEntriesByUserIdAndTagId($this->getUser()->getId(), $tag->getId());
 
             $flatTags[] = [
-                'id' => $tag['id'],
-                'label' => $tag['label'],
-                'slug' => $tag['slug'],
+                'id' => $tag->getId(),
+                'label' => $tag->getLabel(),
+                'slug' => $tag->getSlug(),
                 'nbEntries' => $nbEntries,
             ];
         }
index f2da3f4def670d5a0e08af123a1617a2cb3fa509..dd0f7e67227b2f7d677888de72e30c57b90d0c04 100644 (file)
@@ -19,7 +19,7 @@ use Wallabag\AnnotationBundle\Entity\Annotation;
  *
  * @XmlRoot("entry")
  * @ORM\Entity(repositoryClass="Wallabag\CoreBundle\Repository\EntryRepository")
- * @ORM\Table(name="`entry`")
+ * @ORM\Table(name="`entry`", options={"collate"="utf8mb4_unicode_ci", "charset"="utf8mb4"})
  * @ORM\HasLifecycleCallbacks()
  * @Hateoas\Relation("self", href = "expr('/api/entries/' ~ object.getId())")
  */
@@ -190,10 +190,10 @@ class Entry
      * @ORM\JoinTable(
      *  name="entry_tag",
      *  joinColumns={
-     *      @ORM\JoinColumn(name="entry_id", referencedColumnName="id")
+     *      @ORM\JoinColumn(name="entry_id", referencedColumnName="id", onDelete="cascade")
      *  },
      *  inverseJoinColumns={
-     *      @ORM\JoinColumn(name="tag_id", referencedColumnName="id")
+     *      @ORM\JoinColumn(name="tag_id", referencedColumnName="id", onDelete="cascade")
      *  }
      * )
      */
index cd2b47b9f7c32b737d35910effe1cf53398adac8..14616d8889e7dc2f2d60d93b60beb73e9e87e15f 100644 (file)
@@ -329,4 +329,18 @@ class EntryRepository extends EntityRepository
 
         return $qb->getQuery()->getSingleScalarResult();
     }
+
+    /**
+     * Remove all entries for a user id.
+     * Used when a user want to reset all informations.
+     *
+     * @param int $userId
+     */
+    public function removeAllByUserId($userId)
+    {
+        $this->getEntityManager()
+            ->createQuery('DELETE FROM Wallabag\CoreBundle\Entity\Entry e WHERE e.user = :userId')
+            ->setParameter('userId', $userId)
+            ->execute();
+    }
 }
index e76878d49d6e7cc5bb06c0f2a1d8227ef2bec4bc..81445989b71c97fc06a13e22f8aaada1b81d871d 100644 (file)
@@ -34,6 +34,9 @@ class TagRepository extends EntityRepository
 
     /**
      * Find all tags per user.
+     * Instead of just left joined on the Entry table, we select only id and group by id to avoid tag multiplication in results.
+     * Once we have all tags id, we can safely request them one by one.
+     * This'll still be fastest than the previous query.
      *
      * @param int $userId
      *
@@ -41,15 +44,20 @@ class TagRepository extends EntityRepository
      */
     public function findAllTags($userId)
     {
-        return $this->createQueryBuilder('t')
-            ->select('t.slug', 't.label', 't.id')
+        $ids = $this->createQueryBuilder('t')
+            ->select('t.id')
             ->leftJoin('t.entries', 'e')
             ->where('e.user = :userId')->setParameter('userId', $userId)
-            ->groupBy('t.slug')
-            ->addGroupBy('t.label')
-            ->addGroupBy('t.id')
+            ->groupBy('t.id')
             ->getQuery()
             ->getArrayResult();
+
+        $tags = [];
+        foreach ($ids as $id) {
+            $tags[] = $this->find($id);
+        }
+
+        return $tags;
     }
 
     /**
index a4b727f42b57d21ef4b3a9eedc881370ecca8cef..048a72fc803e30cb340a2409a892e08208cad7b7 100644 (file)
@@ -129,3 +129,10 @@ services:
         arguments:
             - '@twig'
             - '%kernel.debug%'
+
+    wallabag_core.subscriber.sqlite_cascade_delete:
+        class:  Wallabag\CoreBundle\Subscriber\SQLiteCascadeDeleteSubscriber
+        arguments:
+            - "@doctrine"
+        tags:
+            - { name: doctrine.event_subscriber }
index f5548a217a42b0e553c7a0cc85a6fe804a9d858d..7c8ae66e555c5bd41984dcbdadda42ee31f64527 100644 (file)
@@ -89,10 +89,17 @@ config:
         email_label: 'Emailadresse'
         # twoFactorAuthentication_label: 'Two factor authentication'
         delete:
-            # title: Delete my account (danger zone !)
+            # title: Delete my account (a.k.a danger zone)
             # 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.
-            # confirm: Are you really sure? (it can't be UNDONE)
+            # confirm: Are you really sure? (THIS CAN'T BE UNDONE)
             # button: Delete my account
+    reset:
+        # title: Reset area (a.k.a danger zone)
+        # description: By hiting buttons below you'll have ability to remove some informations from your account. Be aware that these actions are IRREVERSIBLE.
+        # annotations: Remove ALL annotations
+        # tags: Remove ALL tags
+        # entries: Remove ALL entries
+        # confirm: Are you really really sure? (THIS CAN'T BE UNDONE)
     form_password:
         old_password_label: 'Gammel adgangskode'
         new_password_label: 'Ny adgangskode'
@@ -462,6 +469,9 @@ flashes:
             # tagging_rules_deleted: 'Tagging rule deleted'
             # user_added: 'User "%username%" added'
             # rss_token_updated: 'RSS token updated'
+            # annotations_reset: Annotations reset
+            # tags_reset: Tags reset
+            # entries_reset: Entries reset
     entry:
         notice:
             # entry_already_saved: 'Entry already saved on %date%'
index 9edd7fb702a5693c8dc19c689ea56a1c2aa8d039..20f9753be53dd70adf8fb51e845ebb3d2dfc3032 100644 (file)
@@ -89,10 +89,17 @@ config:
         email_label: 'E-Mail-Adresse'
         twoFactorAuthentication_label: 'Zwei-Faktor-Authentifizierung'
         delete:
-            # title: Delete my account (danger zone !)
+            # title: Delete my account (a.k.a danger zone)
             # 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.
-            # confirm: Are you really sure? (it can't be UNDONE)
+            # confirm: Are you really sure? (THIS CAN'T BE UNDONE)
             # button: Delete my account
+    reset:
+        # title: Reset area (a.k.a danger zone)
+        # description: By hiting buttons below you'll have ability to remove some informations from your account. Be aware that these actions are IRREVERSIBLE.
+        # annotations: Remove ALL annotations
+        # tags: Remove ALL tags
+        # entries: Remove ALL entries
+        # confirm: Are you really really sure? (THIS CAN'T BE UNDONE)
     form_password:
         old_password_label: 'Altes Kennwort'
         new_password_label: 'Neues Kennwort'
@@ -462,6 +469,9 @@ flashes:
             tagging_rules_deleted: 'Tagging-Regel gelöscht'
             user_added: 'Benutzer "%username%" erstellt'
             rss_token_updated: 'RSS-Token aktualisiert'
+            # annotations_reset: Annotations reset
+            # tags_reset: Tags reset
+            # entries_reset: Entries reset
     entry:
         notice:
             entry_already_saved: 'Eintrag bereits am %date% gespeichert'
index b86145a0f05c0a45a9b424a03fc1a43d5c7d884a..35dde5353bcc4d43c13582dc725ccfcb92d3fd0c 100644 (file)
@@ -89,10 +89,17 @@ config:
         email_label: 'Email'
         twoFactorAuthentication_label: 'Two factor authentication'
         delete:
-            title: Delete my account (danger zone !)
+            title: Delete my account (a.k.a danger zone)
             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.
-            confirm: Are you really sure? (it can't be UNDONE)
+            confirm: Are you really sure? (THIS CAN'T BE UNDONE)
             button: Delete my account
+    reset:
+        title: Reset area (a.k.a danger zone)
+        description: By hiting buttons below you'll have ability to remove some informations from your account. Be aware that these actions are IRREVERSIBLE.
+        annotations: Remove ALL annotations
+        tags: Remove ALL tags
+        entries: Remove ALL entries
+        confirm: Are you really really sure? (THIS CAN'T BE UNDONE)
     form_password:
         old_password_label: 'Current password'
         new_password_label: 'New password'
@@ -461,6 +468,9 @@ flashes:
             tagging_rules_updated: 'Tagging rules updated'
             tagging_rules_deleted: 'Tagging rule deleted'
             rss_token_updated: 'RSS token updated'
+            annotations_reset: Annotations reset
+            tags_reset: Tags reset
+            entries_reset: Entries reset
     entry:
         notice:
             entry_already_saved: 'Entry already saved on %date%'
index b7187f50a20eb6f6bbb2c3ecd278af9cf7c5dd2a..13f2e9776e831fc46d57ca4d881553c966cc417a 100644 (file)
@@ -89,10 +89,17 @@ config:
         email_label: 'Direccion e-mail'
         twoFactorAuthentication_label: 'Autentificación de dos factores'
         delete:
-            # title: Delete my account (danger zone !)
+            # title: Delete my account (a.k.a danger zone)
             # 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.
-            # confirm: Are you really sure? (it can't be UNDONE)
+            # confirm: Are you really sure? (THIS CAN'T BE UNDONE)
             # button: Delete my account
+    reset:
+        # title: Reset area (a.k.a danger zone)
+        # description: By hiting buttons below you'll have ability to remove some informations from your account. Be aware that these actions are IRREVERSIBLE.
+        # annotations: Remove ALL annotations
+        # tags: Remove ALL tags
+        # entries: Remove ALL entries
+        # confirm: Are you really really sure? (THIS CAN'T BE UNDONE)
     form_password:
         old_password_label: 'Contraseña actual'
         new_password_label: 'Nueva contraseña'
@@ -462,6 +469,9 @@ flashes:
             tagging_rules_deleted: 'Regla de etiquetado actualizada'
             user_added: 'Usuario "%username%" añadido'
             rss_token_updated: 'RSS token actualizado'
+            # annotations_reset: Annotations reset
+            # tags_reset: Tags reset
+            # entries_reset: Entries reset
     entry:
         notice:
             entry_already_saved: 'Entrada ya guardada por %fecha%'
index 0751752b762b790374f324831b4b58b0bbfe8ffb..5ee1f62d194247d81ddacca3faa67990c97f73dd 100644 (file)
@@ -89,10 +89,17 @@ config:
         email_label: 'نشانی ایمیل'
         twoFactorAuthentication_label: 'تأیید ۲مرحله‌ای'
         delete:
-            # title: Delete my account (danger zone !)
+            # title: Delete my account (a.k.a danger zone)
             # 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.
-            # confirm: Are you really sure? (it can't be UNDONE)
+            # confirm: Are you really sure? (THIS CAN'T BE UNDONE)
             # button: Delete my account
+    reset:
+        # title: Reset area (a.k.a danger zone)
+        # description: By hiting buttons below you'll have ability to remove some informations from your account. Be aware that these actions are IRREVERSIBLE.
+        # annotations: Remove ALL annotations
+        # tags: Remove ALL tags
+        # entries: Remove ALL entries
+        # confirm: Are you really really sure? (THIS CAN'T BE UNDONE)
     form_password:
         old_password_label: 'رمز قدیمی'
         new_password_label: 'رمز تازه'
@@ -461,6 +468,9 @@ flashes:
             tagging_rules_deleted: 'قانون برچسب‌گذاری پاک شد'
             user_added: 'کابر "%username%" افزوده شد'
             rss_token_updated: 'کد آر-اس-اس به‌روز شد'
+            # annotations_reset: Annotations reset
+            # tags_reset: Tags reset
+            # entries_reset: Entries reset
     entry:
         notice:
             entry_already_saved: 'این مقاله در تاریخ %date% ذخیره شده بود'
index 8d19ccb1f39df12660e6621433190416d74bd728..14bdbbc7574c2cce7962851361f4c72642b370a0 100644 (file)
@@ -91,8 +91,15 @@ config:
         delete:
             title: Supprimer mon compte (attention danger !)
             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é.
-            confirm: Vous êtes vraiment sûr ? (c'est IRRÉVERSIBLE !)
+            confirm: Vous êtes vraiment sûr ? (C'EST IRRÉVERSIBLE)
             button: 'Supprimer mon compte'
+    reset:
+        title: Réinitialisation (attention danger !)
+        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 !
+        annotations: Supprimer TOUTES les annotations
+        tags: Supprimer TOUS les tags
+        entries: Supprimer TOUS les articles
+        confirm: Êtes-vous vraiment vraiment sûr ? (C'EST IRRÉVERSIBLE)
     form_password:
         old_password_label: 'Mot de passe actuel'
         new_password_label: 'Nouveau mot de passe'
@@ -391,7 +398,7 @@ developer:
         field_grant_types: 'Type de privilège accordé'
         no_client: 'Aucun client pour le moment'
     remove:
-        warn_message_1: 'Vous avez la possibilité de supprimer le client %name%. Cette action est IRREVERSIBLE !'
+        warn_message_1: 'Vous avez la possibilité de supprimer le client %name%. Cette action est IRRÉVERSIBLE !'
         warn_message_2: "Si vous supprimez le client %name%, toutes les applications qui l'utilisaient ne fonctionneront plus avec votre compte wallabag."
         action: 'Supprimer le client %name%'
     client:
@@ -462,9 +469,12 @@ flashes:
             tagging_rules_deleted: 'Règle supprimée'
             user_added: 'Utilisateur "%username%" ajouté'
             rss_token_updated: 'Jeton RSS mis à jour'
+            annotations_reset: Annotations supprimées
+            tags_reset: Tags supprimés
+            entries_reset: Articles supprimés
     entry:
         notice:
-            entry_already_saved: 'Article déjà sauvergardé le %date%'
+            entry_already_saved: 'Article déjà sauvegardé le %date%'
             entry_saved: 'Article enregistré'
             entry_saved_failed: 'Article enregistré mais impossible de récupérer le contenu'
             entry_updated: 'Article mis à jour'
index 4d3452ea7762131b0c3e8b0c2210453219e8a02f..bc4448bddb73227d4fe8507149b063ae8eb3f60b 100644 (file)
@@ -89,10 +89,17 @@ config:
         email_label: 'E-mail'
         twoFactorAuthentication_label: 'Two factor authentication'
         delete:
-            # title: Delete my account (danger zone !)
+            # title: Delete my account (a.k.a danger zone)
             # 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.
-            # confirm: Are you really sure? (it can't be UNDONE)
+            # confirm: Are you really sure? (THIS CAN'T BE UNDONE)
             # button: Delete my account
+    reset:
+        # title: Reset area (a.k.a danger zone)
+        # description: By hiting buttons below you'll have ability to remove some informations from your account. Be aware that these actions are IRREVERSIBLE.
+        # annotations: Remove ALL annotations
+        # tags: Remove ALL tags
+        # entries: Remove ALL entries
+        # confirm: Are you really really sure? (THIS CAN'T BE UNDONE)
     form_password:
         old_password_label: 'Password corrente'
         new_password_label: 'Nuova password'
@@ -462,6 +469,9 @@ flashes:
             tagging_rules_deleted: 'Regola di tagging aggiornate'
             user_added: 'Utente "%username%" aggiunto'
             rss_token_updated: 'RSS token aggiornato'
+            # annotations_reset: Annotations reset
+            # tags_reset: Tags reset
+            # entries_reset: Entries reset
     entry:
         notice:
             entry_already_saved: 'Contenuto già salvato in data %date%'
index f14213c606a8c85882452ee6f88e5b3e81e191a6..7d1a801a2907462fa2b6efd4c235542fe466d58c 100644 (file)
@@ -89,10 +89,17 @@ config:
         email_label: 'Adreça de corrièl'
         twoFactorAuthentication_label: 'Dobla autentificacion'
         delete:
-            # title: Delete my account (danger zone !)
+            # title: Delete my account (a.k.a danger zone)
             # 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.
-            # confirm: Are you really sure? (it can't be UNDONE)
+            # confirm: Are you really sure? (THIS CAN'T BE UNDONE)
             # button: Delete my account
+    reset:
+        # title: Reset area (a.k.a danger zone)
+        # description: By hiting buttons below you'll have ability to remove some informations from your account. Be aware that these actions are IRREVERSIBLE.
+        # annotations: Remove ALL annotations
+        # tags: Remove ALL tags
+        # entries: Remove ALL entries
+        # confirm: Are you really really sure? (THIS CAN'T BE UNDONE)
     form_password:
         old_password_label: 'Senhal actual'
         new_password_label: 'Senhal novèl'
@@ -462,6 +469,9 @@ flashes:
             tagging_rules_deleted: 'Règla suprimida'
             user_added: 'Utilizaire "%username%" ajustat'
             rss_token_updated: 'Geton RSS mes a jorn'
+            # annotations_reset: Annotations reset
+            # tags_reset: Tags reset
+            # entries_reset: Entries reset
     entry:
         notice:
             entry_already_saved: 'Article ja salvargardat lo %date%'
index 6f22f90d1a0019e3a81474382649b5c21b891222..b05a9dfd9a62a18547d424e826dafd058c69c7de 100644 (file)
@@ -93,6 +93,13 @@ config:
             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.
             confirm: Jesteś pewien? (tej operacji NIE MOŻNA cofnąć)
             button: Usuń moje konto
+    reset:
+        # title: Reset area (a.k.a danger zone)
+        # description: By hiting buttons below you'll have ability to remove some informations from your account. Be aware that these actions are IRREVERSIBLE.
+        # annotations: Remove ALL annotations
+        # tags: Remove ALL tags
+        # entries: Remove ALL entries
+        # confirm: Are you really really sure? (THIS CAN'T BE UNDONE)
     form_password:
         old_password_label: 'Stare hasło'
         new_password_label: 'Nowe hasło'
@@ -462,6 +469,9 @@ flashes:
             tagging_rules_deleted: 'Reguła tagowania usunięta'
             user_added: 'Użytkownik "%username%" dodany'
             rss_token_updated: 'Token kanału RSS zaktualizowany'
+            # annotations_reset: Annotations reset
+            # tags_reset: Tags reset
+            # entries_reset: Entries reset
     entry:
         notice:
             entry_already_saved: 'Wpis już został dodany %date%'
index 29db9c3e473d088f778b21f328afc7f001d9df7d..571452c042e11da74d4b6189cd7f2a4cefa3dbff 100644 (file)
@@ -89,10 +89,17 @@ config:
         email_label: 'E-mail'
         # twoFactorAuthentication_label: 'Two factor authentication'
         delete:
-            # title: Delete my account (danger zone !)
+            # title: Delete my account (a.k.a danger zone)
             # 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.
-            # confirm: Are you really sure? (it can't be UNDONE)
+            # confirm: Are you really sure? (THIS CAN'T BE UNDONE)
             # button: Delete my account
+    reset:
+        # title: Reset area (a.k.a danger zone)
+        # description: By hiting buttons below you'll have ability to remove some informations from your account. Be aware that these actions are IRREVERSIBLE.
+        # annotations: Remove ALL annotations
+        # tags: Remove ALL tags
+        # entries: Remove ALL entries
+        # confirm: Are you really really sure? (THIS CAN'T BE UNDONE)
     form_password:
         old_password_label: 'Parola veche'
         new_password_label: 'Parola nouă'
@@ -462,6 +469,9 @@ flashes:
             # tagging_rules_deleted: 'Tagging rule deleted'
             # user_added: 'User "%username%" added'
             # rss_token_updated: 'RSS token updated'
+            # annotations_reset: Annotations reset
+            # tags_reset: Tags reset
+            # entries_reset: Entries reset
     entry:
         notice:
             # entry_already_saved: 'Entry already saved on %date%'
index 41e8e576e8060210c2f5b53ee7024c4c7a32fd13..8e42965337f2a08e1cc0dbd1ba19ff744677421e 100644 (file)
@@ -89,10 +89,17 @@ config:
         email_label: 'E-posta'
         twoFactorAuthentication_label: 'İki adımlı doğrulama'
         delete:
-            # title: Delete my account (danger zone !)
+            # title: Delete my account (a.k.a danger zone)
             # 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.
-            # confirm: Are you really sure? (it can't be UNDONE)
+            # confirm: Are you really sure? (THIS CAN'T BE UNDONE)
             # button: Delete my account
+    reset:
+        # title: Reset area (a.k.a danger zone)
+        # description: By hiting buttons below you'll have ability to remove some informations from your account. Be aware that these actions are IRREVERSIBLE.
+        # annotations: Remove ALL annotations
+        # tags: Remove ALL tags
+        # entries: Remove ALL entries
+        # confirm: Are you really really sure? (THIS CAN'T BE UNDONE)
     form_password:
         old_password_label: 'Eski şifre'
         new_password_label: 'Yeni şifre'
@@ -461,6 +468,9 @@ flashes:
             tagging_rules_deleted: 'Tagging rule deleted'
             user_added: 'User "%username%" added'
             rss_token_updated: 'RSS token updated'
+            # annotations_reset: Annotations reset
+            # tags_reset: Tags reset
+            # entries_reset: Entries reset
     entry:
         notice:
             entry_already_saved: 'Entry already saved on %date%'
index 54508b6dcb225f799876d1e418d224fa71ca39f6..455d02950e0f4f4717af39df66740d5e7afa478f 100644 (file)
         </fieldset>
         {% endif %}
 
+        <h2>{{ 'config.reset.title'|trans }}</h2>
+        <fieldset class="w500p inline">
+            <p>{{ 'config.reset.description'|trans }}</p>
+            <ul>
+                <li>
+                    <a href="{{ path('config_reset', { type: 'annotations'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
+                        {{ 'config.reset.annotations'|trans }}
+                    </a>
+                </li>
+                <li>
+                    <a href="{{ path('config_reset', { type: 'tags'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
+                        {{ 'config.reset.tags'|trans }}
+                    </a>
+                </li>
+                <li>
+                    <a href="{{ path('config_reset', { type: 'entries'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
+                        {{ 'config.reset.entries'|trans }}
+                    </a>
+                </li>
+            </ul>
+        </fieldset>
+
         {{ form_widget(form.user._token) }}
         {{ form_widget(form.user.save) }}
     </form>
index 8434508d0552e3b5c9b3217983a928ca8e2e0b53..79826e0f1d2061817a33e6ebaba98c62d23ece53 100644 (file)
                             {{ form_widget(form.user._token) }}
                         </form>
 
+                        <br /><hr /><br />
+
+                        <div class="row">
+                            <h5>{{ 'config.reset.title'|trans }}</h5>
+                            <p>{{ 'config.reset.description'|trans }}</p>
+                            <a href="{{ path('config_reset', { type: 'annotations'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
+                                {{ 'config.reset.annotations'|trans }}
+                            </a>
+                            <a href="{{ path('config_reset', { type: 'tags'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
+                                {{ 'config.reset.tags'|trans }}
+                            </a>
+                            <a href="{{ path('config_reset', { type: 'entries'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
+                                {{ 'config.reset.entries'|trans }}
+                            </a>
+                        </div>
+
                         {% if enabled_users > 1 %}
                             <br /><hr /><br />
 
diff --git a/src/Wallabag/CoreBundle/Subscriber/SQLiteCascadeDeleteSubscriber.php b/src/Wallabag/CoreBundle/Subscriber/SQLiteCascadeDeleteSubscriber.php
new file mode 100644 (file)
index 0000000..f7210bd
--- /dev/null
@@ -0,0 +1,70 @@
+<?php
+
+namespace Wallabag\CoreBundle\Subscriber;
+
+use Doctrine\Common\EventSubscriber;
+use Doctrine\ORM\Event\LifecycleEventArgs;
+use Wallabag\CoreBundle\Entity\Entry;
+use Doctrine\Bundle\DoctrineBundle\Registry;
+
+/**
+ * SQLite doesn't care about cascading remove, so we need to manually remove associated stuf for an Entry.
+ * Foreign Key Support can be enabled by running `PRAGMA foreign_keys = ON;` at runtime (AT RUNTIME !).
+ * But it needs a compilation flag that not all SQLite instance has ...
+ *
+ * @see https://www.sqlite.org/foreignkeys.html#fk_enable
+ */
+class SQLiteCascadeDeleteSubscriber implements EventSubscriber
+{
+    private $doctrine;
+
+    /**
+     * @param \Doctrine\Bundle\DoctrineBundle\Registry $doctrine
+     */
+    public function __construct(Registry $doctrine)
+    {
+        $this->doctrine = $doctrine;
+    }
+
+    /**
+     * @return array
+     */
+    public function getSubscribedEvents()
+    {
+        return [
+            'preRemove',
+        ];
+    }
+
+    /**
+     * We removed everything related to the upcoming removed entry because SQLite can't handle it on it own.
+     * We do it in the preRemove, because we can't retrieve tags in the postRemove (because the entry id is gone).
+     *
+     * @param LifecycleEventArgs $args
+     */
+    public function preRemove(LifecycleEventArgs $args)
+    {
+        $entity = $args->getEntity();
+
+        if (!$this->doctrine->getConnection()->getDriver() instanceof \Doctrine\DBAL\Driver\PDOSqlite\Driver ||
+            !$entity instanceof Entry) {
+            return;
+        }
+
+        $em = $this->doctrine->getManager();
+
+        if (null !== $entity->getTags()) {
+            foreach ($entity->getTags() as $tag) {
+                $entity->removeTag($tag);
+            }
+        }
+
+        if (null !== $entity->getAnnotations()) {
+            foreach ($entity->getAnnotations() as $annotation) {
+                $em->remove($annotation);
+            }
+        }
+
+        $em->flush();
+    }
+}
index eb9c8e676e0cbd03b4f92eda2b502a1f5c88ad32..8062e53f12fd1a76c3a0b1ad3690c0e5e9468473 100644 (file)
@@ -21,7 +21,7 @@ services:
         arguments:
             - WallabagUserBundle:User
 
-    wallabag_user.create_config:
+    wallabag_user.listener.create_config:
         class: Wallabag\UserBundle\EventListener\CreateConfigListener
         arguments:
             - "@doctrine.orm.entity_manager"
index 5faa0130bece27ee59f15b3b0743290f682272cb..8d0644d1c46ea9f9ea2cf8fb2298f7de18dd9867 100644 (file)
@@ -5,6 +5,9 @@ namespace Tests\Wallabag\CoreBundle\Controller;
 use Tests\Wallabag\CoreBundle\WallabagCoreTestCase;
 use Wallabag\CoreBundle\Entity\Config;
 use Wallabag\UserBundle\Entity\User;
+use Wallabag\CoreBundle\Entity\Entry;
+use Wallabag\CoreBundle\Entity\Tag;
+use Wallabag\AnnotationBundle\Entity\Annotation;
 
 class ConfigControllerTest extends WallabagCoreTestCase
 {
@@ -690,4 +693,146 @@ class ConfigControllerTest extends WallabagCoreTestCase
 
         $this->assertEmpty($entries);
     }
+
+    public function testReset()
+    {
+        $this->logInAs('empty');
+        $client = $this->getClient();
+
+        $em = $client->getContainer()->get('doctrine.orm.entity_manager');
+
+        $user = static::$kernel->getContainer()->get('security.token_storage')->getToken()->getUser();
+
+        $tag = new Tag();
+        $tag->setLabel('super');
+        $em->persist($tag);
+
+        $entry = new Entry($user);
+        $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');
+        $entry->setContent('Youhou');
+        $entry->setTitle('Youhou');
+        $entry->addTag($tag);
+        $em->persist($entry);
+
+        $entry2 = new Entry($user);
+        $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');
+        $entry2->setContent('Youhou');
+        $entry2->setTitle('Youhou');
+        $entry2->addTag($tag);
+        $em->persist($entry2);
+
+        $annotation = new Annotation($user);
+        $annotation->setText('annotated');
+        $annotation->setQuote('annotated');
+        $annotation->setRanges([]);
+        $annotation->setEntry($entry);
+        $em->persist($annotation);
+
+        $em->flush();
+
+        // reset annotations
+        $crawler = $client->request('GET', '/config#set3');
+
+        $this->assertEquals(200, $client->getResponse()->getStatusCode());
+
+        $crawler = $client->click($crawler->selectLink('config.reset.annotations')->link());
+
+        $this->assertEquals(302, $client->getResponse()->getStatusCode());
+        $this->assertContains('flashes.config.notice.annotations_reset', $client->getContainer()->get('session')->getFlashBag()->get('notice')[0]);
+
+        $annotationsReset = $em
+            ->getRepository('WallabagAnnotationBundle:Annotation')
+            ->findAnnotationsByPageId($entry->getId(), $user->getId());
+
+        $this->assertEmpty($annotationsReset, 'Annotations were reset');
+
+        // reset tags
+        $crawler = $client->request('GET', '/config#set3');
+
+        $this->assertEquals(200, $client->getResponse()->getStatusCode());
+
+        $crawler = $client->click($crawler->selectLink('config.reset.tags')->link());
+
+        $this->assertEquals(302, $client->getResponse()->getStatusCode());
+        $this->assertContains('flashes.config.notice.tags_reset', $client->getContainer()->get('session')->getFlashBag()->get('notice')[0]);
+
+        $tagReset = $em
+            ->getRepository('WallabagCoreBundle:Tag')
+            ->countAllTags($user->getId());
+
+        $this->assertEquals(0, $tagReset, 'Tags were reset');
+
+        // reset entries
+        $crawler = $client->request('GET', '/config#set3');
+
+        $this->assertEquals(200, $client->getResponse()->getStatusCode());
+
+        $crawler = $client->click($crawler->selectLink('config.reset.entries')->link());
+
+        $this->assertEquals(302, $client->getResponse()->getStatusCode());
+        $this->assertContains('flashes.config.notice.entries_reset', $client->getContainer()->get('session')->getFlashBag()->get('notice')[0]);
+
+        $entryReset = $em
+            ->getRepository('WallabagCoreBundle:Entry')
+            ->countAllEntriesByUsername($user->getId());
+
+        $this->assertEquals(0, $entryReset, 'Entries were reset');
+    }
+
+    public function testResetEntriesCascade()
+    {
+        $this->logInAs('empty');
+        $client = $this->getClient();
+
+        $em = $client->getContainer()->get('doctrine.orm.entity_manager');
+
+        $user = static::$kernel->getContainer()->get('security.token_storage')->getToken()->getUser();
+
+        $tag = new Tag();
+        $tag->setLabel('super');
+        $em->persist($tag);
+
+        $entry = new Entry($user);
+        $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');
+        $entry->setContent('Youhou');
+        $entry->setTitle('Youhou');
+        $entry->addTag($tag);
+        $em->persist($entry);
+
+        $annotation = new Annotation($user);
+        $annotation->setText('annotated');
+        $annotation->setQuote('annotated');
+        $annotation->setRanges([]);
+        $annotation->setEntry($entry);
+        $em->persist($annotation);
+
+        $em->flush();
+
+        $crawler = $client->request('GET', '/config#set3');
+
+        $this->assertEquals(200, $client->getResponse()->getStatusCode());
+
+        $crawler = $client->click($crawler->selectLink('config.reset.entries')->link());
+
+        $this->assertEquals(302, $client->getResponse()->getStatusCode());
+        $this->assertContains('flashes.config.notice.entries_reset', $client->getContainer()->get('session')->getFlashBag()->get('notice')[0]);
+
+        $entryReset = $em
+            ->getRepository('WallabagCoreBundle:Entry')
+            ->countAllEntriesByUsername($user->getId());
+
+        $this->assertEquals(0, $entryReset, 'Entries were reset');
+
+        $tagReset = $em
+            ->getRepository('WallabagCoreBundle:Tag')
+            ->countAllTags($user->getId());
+
+        $this->assertEquals(0, $tagReset, 'Tags were reset');
+
+        $annotationsReset = $em
+            ->getRepository('WallabagAnnotationBundle:Annotation')
+            ->findAnnotationsByPageId($entry->getId(), $user->getId());
+
+        $this->assertEmpty($annotationsReset, 'Annotations were reset');
+    }
 }