From: Jeremy Benoist Date: Mon, 24 Oct 2016 10:03:17 +0000 (+0200) Subject: Merge remote-tracking branch 'origin/master' into 2.2 X-Git-Tag: 2.2.0~3^2~83 X-Git-Url: https://git.immae.eu/?a=commitdiff_plain;h=64a8781e453c40ff144d03405abe2dc1ccfacbe0;hp=c64b4941d5222586a82d2c8d6a61969bb9cfe96b;p=github%2Fwallabag%2Fwallabag.git Merge remote-tracking branch 'origin/master' into 2.2 --- 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 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'); } 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 @@ +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 index 00000000..5cce55a5 --- /dev/null +++ b/app/DoctrineMigrations/Version20161022134138.php @@ -0,0 +1,77 @@ +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;'); + + } +} 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: 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 @@ -113,12 +113,26 @@ swiftmailer: fos_rest: param_fetcher_listener: true body_listener: true - format_listener: true view: + mime_types: + csv: + - 'text/csv' + - 'text/plain' + pdf: + - 'application/pdf' + epub: + - 'application/epub+zip' + mobi: + - 'application/x-mobipocket-ebook' view_response_listener: 'force' formats: xml: true - json : true + json: true + txt: true + csv: true + pdf: true + epub: true + mobi: true templating_formats: html: true force_redirects: @@ -127,10 +141,21 @@ fos_rest: default_engine: twig routing_loader: default_format: json + format_listener: + enabled: true + rules: + - { path: "^/api/entries/([0-9]+)/export.(.*)", priorities: ['epub', 'mobi', 'pdf', 'txt', 'csv'], fallback_format: false, prefer_extension: false } + - { path: "^/api", priorities: ['json', 'xml'], fallback_format: false, prefer_extension: false } + - { path: "^/annotations", priorities: ['json', 'xml'], fallback_format: false, prefer_extension: false } + # for an unknown reason, EACH REQUEST goes to FOS\RestBundle\EventListener\FormatListener + # so we need to add custom rule for custom api export but also for all other routes of the application... + - { path: '^/', priorities: ['text/html', '*/*'], fallback_format: html, prefer_extension: false } nelmio_api_doc: sandbox: enabled: false + cache: + enabled: true name: wallabag API documentation nelmio_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: 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: 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: 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 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: test_database_user: null test_database_password: null test_database_path: '%kernel.root_dir%/../data/db/wallabag_test.sqlite' + 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 @@ Rest_Wallabag: - type : rest - resource: "@WallabagApiBundle/Resources/config/routing_rest.yml" - + type : rest + resource: "@WallabagApiBundle/Resources/config/routing_rest.yml" 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: test_database_user: root test_database_password: ~ test_database_path: ~ + 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: test_database_user: travis test_database_password: ~ test_database_path: ~ + 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: test_database_user: ~ test_database_password: ~ test_database_path: "%kernel.root_dir%/../data/db/wallabag_test.sqlite" + 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 @@ "sensio/framework-extra-bundle": "^3.0.2", "incenteev/composer-parameter-handler": "^2.0", "nelmio/cors-bundle": "~1.4.0", - "friendsofsymfony/rest-bundle": "~1.4", - "jms/serializer-bundle": "~1.0", + "friendsofsymfony/rest-bundle": "~2.1", + "jms/serializer-bundle": "~1.1", "nelmio/api-doc-bundle": "~2.7", "mgargano/simplehtmldom": "~1.5", - "tecnickcom/tcpdf": "~6.2", + "tecnickcom/tc-lib-pdf": "dev-master", "simplepie/simplepie": "~1.3.1", "willdurand/hateoas-bundle": "~1.0", "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 You can change your name, your email address and enable ``Two factor authentication``. +If the wallabag instance has more than one enabled user, you can delete your account here. **Take care, we delete all your data**. + Two factor authentication ~~~~~~~~~~~~~~~~~~~~~~~~~ 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? "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" "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_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" 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 Vous pouvez ici modifier votre nom, votre adresse email et activer la ``Double authentification``. +Si l'instance de wallabag compte plus d'un utilisateur actif, vous pouvez supprimer ici votre compte. **Attention, nous supprimons toutes vos données**. + Double authentification (2FA) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 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 @@ namespace Wallabag\AnnotationBundle\Controller; use FOS\RestBundle\Controller\FOSRestController; -use Nelmio\ApiDocBundle\Annotation\ApiDoc; +use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\Response; use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; use Wallabag\AnnotationBundle\Entity\Annotation; use Wallabag\CoreBundle\Entity\Entry; @@ -15,42 +14,35 @@ class WallabagAnnotationController extends FOSRestController /** * Retrieve annotations for an entry. * - * @ApiDoc( - * requirements={ - * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"} - * } - * ) + * @param Entry $entry + * + * @see Wallabag\ApiBundle\Controller\WallabagRestController * - * @return Response + * @return JsonResponse */ public function getAnnotationsAction(Entry $entry) { $annotationRows = $this - ->getDoctrine() - ->getRepository('WallabagAnnotationBundle:Annotation') - ->findAnnotationsByPageId($entry->getId(), $this->getUser()->getId()); + ->getDoctrine() + ->getRepository('WallabagAnnotationBundle:Annotation') + ->findAnnotationsByPageId($entry->getId(), $this->getUser()->getId()); $total = count($annotationRows); $annotations = ['total' => $total, 'rows' => $annotationRows]; $json = $this->get('serializer')->serialize($annotations, 'json'); - return $this->renderJsonResponse($json); + return (new JsonResponse())->setJson($json); } /** * Creates a new annotation. * - * @param Entry $entry + * @param Request $request + * @param Entry $entry * - * @ApiDoc( - * requirements={ - * {"name"="ranges", "dataType"="array", "requirement"="\w+", "description"="The range array for the annotation"}, - * {"name"="quote", "dataType"="string", "required"=false, "description"="Optional, quote for the annotation"}, - * {"name"="text", "dataType"="string", "required"=true, "description"=""}, - * } - * ) + * @return JsonResponse * - * @return Response + * @see Wallabag\ApiBundle\Controller\WallabagRestController */ public function postAnnotationAction(Request $request, Entry $entry) { @@ -75,21 +67,20 @@ class WallabagAnnotationController extends FOSRestController $json = $this->get('serializer')->serialize($annotation, 'json'); - return $this->renderJsonResponse($json); + return (new JsonResponse())->setJson($json); } /** * Updates an annotation. * - * @ApiDoc( - * requirements={ - * {"name"="annotation", "dataType"="string", "requirement"="\w+", "description"="The annotation ID"} - * } - * ) + * @see Wallabag\ApiBundle\Controller\WallabagRestController * * @ParamConverter("annotation", class="WallabagAnnotationBundle:Annotation") * - * @return Response + * @param Annotation $annotation + * @param Request $request + * + * @return JsonResponse */ public function putAnnotationAction(Annotation $annotation, Request $request) { @@ -104,21 +95,19 @@ class WallabagAnnotationController extends FOSRestController $json = $this->get('serializer')->serialize($annotation, 'json'); - return $this->renderJsonResponse($json); + return (new JsonResponse())->setJson($json); } /** * Removes an annotation. * - * @ApiDoc( - * requirements={ - * {"name"="annotation", "dataType"="string", "requirement"="\w+", "description"="The annotation ID"} - * } - * ) + * @see Wallabag\ApiBundle\Controller\WallabagRestController * * @ParamConverter("annotation", class="WallabagAnnotationBundle:Annotation") * - * @return Response + * @param Annotation $annotation + * + * @return JsonResponse */ public function deleteAnnotationAction(Annotation $annotation) { @@ -128,19 +117,6 @@ class WallabagAnnotationController extends FOSRestController $json = $this->get('serializer')->serialize($annotation, 'json'); - return $this->renderJsonResponse($json); - } - - /** - * Send a JSON Response. - * We don't use the Symfony JsonRespone, because it takes an array as parameter instead of a JSON string. - * - * @param string $json - * - * @return Response - */ - private function renderJsonResponse($json, $code = 200) - { - return new Response($json, $code, ['application/json']); + return (new JsonResponse())->setJson($json); } } 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 * @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; 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 { return $this->createQueryBuilder('a') ->andWhere('a.id = :annotationId')->setParameter('annotationId', $annotationId) - ->getQuery()->getSingleResult() + ->getQuery() + ->getSingleResult() ; } @@ -67,7 +68,8 @@ class AnnotationRepository extends EntityRepository return $this->createQueryBuilder('a') ->where('a.entry = :entryId')->setParameter('entryId', $entryId) ->andwhere('a.user = :userId')->setParameter('userId', $userId) - ->getQuery()->getResult() + ->getQuery() + ->getResult() ; } @@ -106,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(); + } } 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 @@ namespace Wallabag\ApiBundle\Controller; use FOS\RestBundle\Controller\FOSRestController; -use Hateoas\Configuration\Route; +use Hateoas\Configuration\Route as HateoasRoute; use Hateoas\Representation\Factory\PagerfantaFactory; use Nelmio\ApiDocBundle\Annotation\ApiDoc; +use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Security\Core\Exception\AccessDeniedException; use Wallabag\CoreBundle\Entity\Entry; use Wallabag\CoreBundle\Entity\Tag; +use Wallabag\AnnotationBundle\Entity\Annotation; class WallabagRestController extends FOSRestController { @@ -115,7 +117,7 @@ class WallabagRestController extends FOSRestController $pagerfantaFactory = new PagerfantaFactory('page', 'perPage'); $paginatedCollection = $pagerfantaFactory->createRepresentation( $pager, - new Route( + new HateoasRoute( 'api_get_entries', [ 'archive' => $isArchived, @@ -157,6 +159,28 @@ class WallabagRestController extends FOSRestController return (new JsonResponse())->setJson($json); } + /** + * Retrieve a single entry as a predefined format. + * + * @ApiDoc( + * requirements={ + * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"} + * } + * ) + * + * @return Response + */ + public function getEntryExportAction(Entry $entry, Request $request) + { + $this->validateAuthentication(); + $this->validateUserAccess($entry->getUser()->getId()); + + return $this->get('wallabag_core.helper.entries_export') + ->setEntries($entry) + ->updateTitle('entry') + ->exportAs($request->attributes->get('_format')); + } + /** * Create an entry. * @@ -495,6 +519,104 @@ class WallabagRestController extends FOSRestController return (new JsonResponse())->setJson($json); } + /** + * Retrieve annotations for an entry. + * + * @ApiDoc( + * requirements={ + * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"} + * } + * ) + * + * @param Entry $entry + * + * @return JsonResponse + */ + public function getAnnotationsAction(Entry $entry) + { + $this->validateAuthentication(); + + return $this->forward('WallabagAnnotationBundle:WallabagAnnotation:getAnnotations', [ + 'entry' => $entry, + ]); + } + + /** + * Creates a new annotation. + * + * @ApiDoc( + * requirements={ + * {"name"="ranges", "dataType"="array", "requirement"="\w+", "description"="The range array for the annotation"}, + * {"name"="quote", "dataType"="string", "required"=false, "description"="Optional, quote for the annotation"}, + * {"name"="text", "dataType"="string", "required"=true, "description"=""}, + * } + * ) + * + * @param Request $request + * @param Entry $entry + * + * @return JsonResponse + */ + public function postAnnotationAction(Request $request, Entry $entry) + { + $this->validateAuthentication(); + + return $this->forward('WallabagAnnotationBundle:WallabagAnnotation:postAnnotation', [ + 'request' => $request, + 'entry' => $entry, + ]); + } + + /** + * Updates an annotation. + * + * @ApiDoc( + * requirements={ + * {"name"="annotation", "dataType"="string", "requirement"="\w+", "description"="The annotation ID"} + * } + * ) + * + * @ParamConverter("annotation", class="WallabagAnnotationBundle:Annotation") + * + * @param Annotation $annotation + * @param Request $request + * + * @return JsonResponse + */ + public function putAnnotationAction(Annotation $annotation, Request $request) + { + $this->validateAuthentication(); + + return $this->forward('WallabagAnnotationBundle:WallabagAnnotation:putAnnotation', [ + 'annotation' => $annotation, + 'request' => $request, + ]); + } + + /** + * Removes an annotation. + * + * @ApiDoc( + * requirements={ + * {"name"="annotation", "dataType"="string", "requirement"="\w+", "description"="The annotation ID"} + * } + * ) + * + * @ParamConverter("annotation", class="WallabagAnnotationBundle:Annotation") + * + * @param Annotation $annotation + * + * @return JsonResponse + */ + public function deleteAnnotationAction(Annotation $annotation) + { + $this->validateAuthentication(); + + return $this->forward('WallabagAnnotationBundle:WallabagAnnotation:deleteAnnotation', [ + 'annotation' => $annotation, + ]); + } + /** * Retrieve version number. * 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 @@ -entries: - type: rest - resource: "WallabagApiBundle:WallabagRest" - name_prefix: api_ +api: + type: rest + resource: "WallabagApiBundle:WallabagRest" + 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 { $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('Installing Wallabag...'); + $output->writeln('Installing wallabag...'); $output->writeln(''); $this @@ -65,7 +65,7 @@ class InstallCommand extends ContainerAwareCommand ->setupConfig() ; - $output->writeln('Wallabag has been successfully installed.'); + $output->writeln('wallabag has been successfully installed.'); $output->writeln('Just execute `php bin/console server:run --env=prod` for using wallabag: http://localhost:8000'); } @@ -77,7 +77,7 @@ class InstallCommand extends ContainerAwareCommand // testing if database driver exists $fulfilled = true; - $label = 'PDO Driver'; + $label = 'PDO Driver (%s)'; $status = 'OK!'; $help = ''; @@ -87,7 +87,7 @@ class InstallCommand extends ContainerAwareCommand $help = 'Database driver "'.$this->getContainer()->getParameter('database_driver').'" is not installed.'; } - $rows[] = [$label, $status, $help]; + $rows[] = [sprintf($label, $this->getContainer()->getParameter('database_driver')), $status, $help]; // testing if connection to the database can be etablished $label = 'Database connection'; @@ -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[] = [ + 'Database version', + 'ERROR!', + 'Your MySQL version ('.$version.') is too old, consider upgrading ('.$minimalVersion.'+).', + ]; + } + } + foreach ($this->functionExists as $functionRequired) { $label = ''.$functionRequired.''; $status = 'OK!'; @@ -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('Success! Your system can run Wallabag properly.'); + $this->defaultOutput->writeln('Success! Your system can run wallabag properly.'); $this->defaultOutput->writeln(''); 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; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; use Wallabag\CoreBundle\Entity\Config; use Wallabag\CoreBundle\Entity\TaggingRule; use Wallabag\CoreBundle\Form\Type\ConfigType; @@ -148,6 +149,9 @@ class ConfigController extends Controller 'token' => $config->getRssToken(), ], 'twofactor_auth' => $this->getParameter('twofactor_auth'), + 'enabled_users' => $this->getDoctrine() + ->getRepository('WallabagUserBundle:User') + ->getSumEnabledUsers(), ]); } @@ -220,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. * @@ -251,4 +329,37 @@ class ConfigController extends Controller return $config; } + + /** + * Delete account for current user. + * + * @Route("/account/delete", name="delete_account") + * + * @param Request $request + * + * @throws AccessDeniedHttpException + * + * @return \Symfony\Component\HttpFoundation\RedirectResponse + */ + public function deleteAccountAction(Request $request) + { + $enabledUsers = $this->getDoctrine() + ->getRepository('WallabagUserBundle:User') + ->getSumEnabledUsers(); + + if ($enabledUsers <= 1) { + throw new AccessDeniedHttpException(); + } + + $user = $this->getUser(); + + // logout current user + $this->get('security.token_storage')->setToken(null); + $request->getSession()->invalidate(); + + $em = $this->get('fos_user.user_manager'); + $em->deleteUser($user); + + return $this->redirect($this->generateUrl('fos_user_security_login')); + } } 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 $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, ]; } 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; * * @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") * } * ) */ 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 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(); + } } 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 /** * 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; } /** 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: arguments: - '@twig' - '%kernel.debug%' + + wallabag_core.subscriber.sqlite_cascade_delete: + class: Wallabag\CoreBundle\Subscriber\SQLiteCascadeDeleteSubscriber + arguments: + - "@doctrine" + tags: + - { 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: name_label: 'Navn' email_label: 'Emailadresse' # twoFactorAuthentication_label: 'Two factor authentication' + delete: + # 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? (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' @@ -460,6 +472,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%' 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: name_label: 'Name' email_label: 'E-Mail-Adresse' twoFactorAuthentication_label: 'Zwei-Faktor-Authentifizierung' + delete: + # 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? (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' @@ -460,6 +472,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' 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: name_label: 'Name' email_label: 'Email' twoFactorAuthentication_label: 'Two factor authentication' + delete: + 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? (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' @@ -459,6 +471,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%' 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: name_label: 'Nombre' email_label: 'Direccion e-mail' twoFactorAuthentication_label: 'Autentificación de dos factores' + delete: + # 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? (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' @@ -460,6 +472,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%' 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: name_label: 'نام' email_label: 'نشانی ایمیل' twoFactorAuthentication_label: 'تأیید ۲مرحله‌ای' + delete: + # 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? (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: 'رمز تازه' @@ -459,6 +471,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% ذخیره شده بود' 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: name_label: 'Nom' email_label: 'Adresse e-mail' twoFactorAuthentication_label: 'Double authentification' + 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) + 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' @@ -386,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: @@ -460,9 +472,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' 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: name_label: 'Nome' email_label: 'E-mail' twoFactorAuthentication_label: 'Two factor authentication' + delete: + # 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? (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' @@ -460,6 +472,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%' 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: internal_settings: 'Configuracion interna' import: 'Importar' howto: 'Ajuda' - developer: 'Desvolopador' + developer: 'Desvolopaire' logout: 'Desconnexion' about: 'A prepaus' search: 'Cercar' save_link: 'Enregistrar un novèl article' back_to_unread: 'Tornar als articles pas legits' - # users_management: 'Users management' + users_management: 'Gestion dels utilizaires' top: add_new_entry: 'Enregistrar un novèl article' search: 'Cercar' @@ -46,7 +46,7 @@ footer: social: 'Social' powered_by: 'propulsat per' about: 'A prepaus' - # stats: Since %user_creation% you read %nb_archives% articles. That is about %per_day% a day! + stats: "Dempuèi %user_creation% avètz legit %nb_archives% articles. Es a l'entorn de %per_day% per jorn !" config: page_title: 'Configuracion' @@ -88,6 +88,18 @@ config: name_label: 'Nom' email_label: 'Adreça de corrièl' twoFactorAuthentication_label: 'Dobla autentificacion' + delete: + # 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? (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' @@ -96,7 +108,7 @@ config: if_label: 'se' then_tag_as_label: 'alara atribuir las etiquetas' delete_rule_label: 'suprimir' - # edit_rule_label: 'edit' + edit_rule_label: 'modificar' rule_label: 'Règla' tags_label: 'Etiquetas' faq: @@ -209,7 +221,7 @@ entry: is_public_label: 'Public' save_label: 'Enregistrar' public: - # shared_by_wallabag: "This article has been shared by wallabag" + shared_by_wallabag: "Aqueste article es estat partejat per wallabag" about: page_title: 'A prepaus' @@ -265,14 +277,14 @@ howto: quickstart: page_title: 'Per ben començar' - # more: 'More…' + more: 'Mai…' intro: title: 'Benvenguda sus wallabag !' 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." paragraph_2: 'Seguètz-nos ' configure: - title: "Configuratz l'aplicacio" - # description: 'In order to have an application which suits you, have a look into the configuration of wallabag.' + title: "Configuratz l'aplicacion" + description: "Per fin d'aver una aplicacion que vos va ben, anatz veire la configuracion de wallabag." language: "Cambiatz la lenga e l'estil de l'aplicacion" rss: 'Activatz los fluxes RSS' tagging_rules: 'Escrivètz de règlas per classar automaticament vòstres articles' @@ -286,7 +298,7 @@ quickstart: import: 'Configurar los impòrt' first_steps: title: 'Primièrs passes' - # 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." + 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." new_article: 'Ajustatz vòstre primièr article' unread_articles: 'E racaptatz-lo !' migrate: @@ -298,14 +310,14 @@ quickstart: readability: 'Migrar dempuèi Readability' instapaper: 'Migrar dempuèi Instapaper' developer: - title: 'Pels desvolopadors' - # description: 'We also thought to the developers: Docker, API, translations, etc.' + title: 'Pels desvolopaires' + description: 'Avèm tanben pensat als desvolopaires : Docker, API, traduccions, etc.' create_application: 'Crear vòstra aplicacion tèrça' - # use_docker: 'Use Docker to install wallabag' + use_docker: 'Utilizar Docker per installar wallabag' docs: title: 'Documentacion complèta' - # 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." - annotate: 'Anotatar vòstre article' + description: "I a un fum de fonccionalitats dins wallabag. Esitetz pas a legir lo manual per las conéisser e aprendre a las utilizar." + annotate: 'Anotar vòstre article' export: 'Convertissètz vòstres articles en ePub o en PDF' search_filters: "Aprenètz a utilizar lo motor de recèrca e los filtres per retrobar l'article que vos interèssa" fetching_errors: "Qué far se mon article es pas recuperat coma cal ?" @@ -390,7 +402,7 @@ developer: warn_message_2: "Se suprimissètz un client, totas las aplicacions que l'emplegan foncionaràn pas mai amb vòstre compte wallabag." action: 'Suprimir aqueste client' client: - page_title: 'Desvlopador > Novèl client' + page_title: 'Desvolopaire > Novèl client' page_description: "Anatz crear un novèl client. Mercés de cumplir l'url de redireccion cap a vòstra aplicacion." form: name_label: "Nom del client" @@ -398,7 +410,7 @@ developer: save_label: 'Crear un novèl client' action_back: 'Retorn' client_parameter: - page_title: 'Desvolopador > Los paramètres de vòstre client' + page_title: 'Desvolopaire > Los paramètres de vòstre client' page_description: 'Vaquí los paramètres de vòstre client' field_name: 'Nom del client' field_id: 'ID Client' @@ -406,7 +418,7 @@ developer: back: 'Retour' read_howto: 'Legir "cossí crear ma primièra aplicacion"' howto: - page_title: 'Desvolopador > Cossí crear ma primièra aplicacion' + page_title: 'Desvolopaire > Cossí crear ma primièra aplicacion' description: paragraph_1: "Las comandas seguentas utilizan la bibliotèca HTTPie. Asseguratz-vos que siasqueòu installadas abans de l'utilizar." paragraph_2: "Vos cal un geton per escambiar entre vòstra aplicacion e l'API de wallabar." @@ -419,31 +431,31 @@ developer: back: 'Retorn' user: - # page_title: Users management - # new_user: Create a new user - # edit_user: Edit an existing user - # description: "Here you can manage all users (create, edit and delete)" - # list: - # actions: Actions - # edit_action: Edit - # yes: Yes - # no: No - # create_new_one: Create a new user + page_title: 'Gestion dels utilizaires' + new_user: 'Crear un novèl utilizaire' + edit_user: 'Modificar un utilizaire existent' + description: "Aquí podètz gerir totes los utilizaires (crear, modificar e suprimir)" + list: + actions: 'Accions' + edit_action: 'Modificar' + yes: 'Òc' + no: 'Non' + create_new_one: 'Crear un novèl utilizaire' form: username_label: "Nom d'utilizaire" - # name_label: 'Name' + name_label: 'Nom' password_label: 'Senhal' repeat_new_password_label: 'Confirmatz vòstre novèl senhal' plain_password_label: 'Senhal en clar' email_label: 'Adreça de corrièl' - # enabled_label: 'Enabled' - # locked_label: 'Locked' - # last_login_label: 'Last login' - # twofactor_label: Two factor authentication - # save: Save - # delete: Delete - # delete_confirm: Are you sure? - # back_to_list: Back to list + enabled_label: 'Actiu' + locked_label: 'Varrolhat' + last_login_label: 'Darrièra connexion' + twofactor_label: 'Autentificacion doble-factor' + save: 'Enregistrar' + delete: 'Suprimir' + delete_confirm: 'Sètz segur ?' + back_to_list: 'Tornar a la lista' error: # page_title: An error occurred @@ -458,8 +470,11 @@ flashes: rss_updated: 'La configuracion dels fluxes RSS es ben estada mesa a jorn' tagging_rules_updated: 'Règlas misa a jorn' tagging_rules_deleted: 'Règla suprimida' - user_added: 'Utilizaire "%username%" apondut' + 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%' @@ -470,12 +485,12 @@ flashes: entry_reloaded_failed: "L'article es estat cargat de nòu mai la recuperacion del contengut a fracassat" entry_archived: 'Article marcat coma legit' entry_unarchived: 'Article marcat coma pas legit' - entry_starred: 'Article apondut dins los favorits' + entry_starred: 'Article ajustat dins los favorits' entry_unstarred: 'Article quitat dels favorits' entry_deleted: 'Article suprimit' tag: notice: - tag_added: 'Etiqueta aponduda' + tag_added: 'Etiqueta ajustada' import: notice: 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: name_label: 'Nazwa' email_label: 'Adres email' twoFactorAuthentication_label: 'Autoryzacja dwuetapowa' + delete: + title: Usuń moje konto (niebezpieczna strefa !) + 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' @@ -460,6 +472,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%' 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: name_label: 'Nume' email_label: 'E-mail' # twoFactorAuthentication_label: 'Two factor authentication' + delete: + # 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? (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ă' @@ -460,6 +472,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%' 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: name_label: 'İsim' email_label: 'E-posta' twoFactorAuthentication_label: 'İki adımlı doğrulama' + delete: + # 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? (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' @@ -459,6 +471,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%' 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 @@ {% endif %} +

{{ 'config.reset.title'|trans }}

+
+

{{ 'config.reset.description'|trans }}

+ +
+ {{ form_widget(form.user._token) }} {{ form_widget(form.user.save) }} + {% if enabled_users > 1 %} +

{{ 'config.form_user.delete.title'|trans }}

+ +

{{ 'config.form_user.delete.description'|trans }}

+ + {{ 'config.form_user.delete.button'|trans }} + + {% endif %} +

{{ 'config.tab_menu.password'|trans }}

{{ 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 @@ {{ form_widget(form.user.save, {'attr': {'class': 'btn waves-effect waves-light'}}) }} {{ form_widget(form.user._token) }} + +


+ +
+
{{ 'config.reset.title'|trans }}
+

{{ 'config.reset.description'|trans }}

+ + {{ 'config.reset.annotations'|trans }} + + + {{ 'config.reset.tags'|trans }} + + + {{ 'config.reset.entries'|trans }} + +
+ + {% if enabled_users > 1 %} +


+ +
+
{{ 'config.form_user.delete.title'|trans }}
+

{{ 'config.form_user.delete.description'|trans }}

+ +
+ {% endif %}
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 @@ +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(); + } +} 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 ->getQuery() ->getSingleResult(); } + + /** + * Count how many users are enabled. + * + * @return int + */ + public function getSumEnabledUsers() + { + return $this->createQueryBuilder('u') + ->select('count(u)') + ->andWhere('u.expired = false') + ->getQuery() + ->getSingleScalarResult(); + } } 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: arguments: - WallabagUserBundle:User - wallabag_user.create_config: + wallabag_user.listener.create_config: class: Wallabag\UserBundle\EventListener\CreateConfigListener arguments: - "@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 @@ namespace Tests\AnnotationBundle\Controller; use Tests\Wallabag\AnnotationBundle\WallabagAnnotationTestCase; +use Wallabag\AnnotationBundle\Entity\Annotation; +use Wallabag\CoreBundle\Entity\Entry; class AnnotationControllerTest extends WallabagAnnotationTestCase { - public function testGetAnnotations() + /** + * This data provider allow to tests annotation from the : + * - API POV (when user use the api to manage annotations) + * - and User POV (when user use the web interface - using javascript - to manage annotations) + */ + public function dataForEachAnnotations() { - $annotation = $this->client->getContainer() - ->get('doctrine.orm.entity_manager') - ->getRepository('WallabagAnnotationBundle:Annotation') - ->findOneByUsername('admin'); + return [ + ['/api/annotations'], + ['annotations'], + ]; + } + + /** + * Test fetching annotations for an entry. + * + * @dataProvider dataForEachAnnotations + */ + public function testGetAnnotations($prefixUrl) + { + $em = $this->client->getContainer()->get('doctrine.orm.entity_manager'); + + $user = $em + ->getRepository('WallabagUserBundle:User') + ->findOneByUserName('admin'); + $entry = $em + ->getRepository('WallabagCoreBundle:Entry') + ->findOneByUsernameAndNotArchived('admin'); - if (!$annotation) { - $this->markTestSkipped('No content found in db.'); + $annotation = new Annotation($user); + $annotation->setEntry($entry); + $annotation->setText('This is my annotation /o/'); + $annotation->setQuote('content'); + + $em->persist($annotation); + $em->flush(); + + if ('annotations' === $prefixUrl) { + $this->logInAs('admin'); } - $this->logInAs('admin'); - $crawler = $this->client->request('GET', 'annotations/'.$annotation->getEntry()->getId().'.json'); + $this->client->request('GET', $prefixUrl.'/'.$entry->getId().'.json'); $this->assertEquals(200, $this->client->getResponse()->getStatusCode()); $content = json_decode($this->client->getResponse()->getContent(), true); - $this->assertEquals(1, $content['total']); + $this->assertGreaterThanOrEqual(1, $content['total']); $this->assertEquals($annotation->getText(), $content['rows'][0]['text']); + + // we need to re-fetch the annotation becase after the flush, it has been "detached" from the entity manager + $annotation = $em->getRepository('WallabagAnnotationBundle:Annotation')->findAnnotationById($annotation->getId()); + $em->remove($annotation); + $em->flush(); } - public function testSetAnnotation() + /** + * Test creating an annotation for an entry. + * + * @dataProvider dataForEachAnnotations + */ + public function testSetAnnotation($prefixUrl) { - $this->logInAs('admin'); + $em = $this->client->getContainer()->get('doctrine.orm.entity_manager'); - $entry = $this->client->getContainer() - ->get('doctrine.orm.entity_manager') + if ('annotations' === $prefixUrl) { + $this->logInAs('admin'); + } + + /** @var Entry $entry */ + $entry = $em ->getRepository('WallabagCoreBundle:Entry') ->findOneByUsernameAndNotArchived('admin'); @@ -41,7 +86,7 @@ class AnnotationControllerTest extends WallabagAnnotationTestCase 'quote' => 'my quote', 'ranges' => ['start' => '', 'startOffset' => 24, 'end' => '', 'endOffset' => 31], ]); - $crawler = $this->client->request('POST', 'annotations/'.$entry->getId().'.json', [], [], $headers, $content); + $this->client->request('POST', $prefixUrl.'/'.$entry->getId().'.json', [], [], $headers, $content); $this->assertEquals(200, $this->client->getResponse()->getStatusCode()); @@ -52,6 +97,7 @@ class AnnotationControllerTest extends WallabagAnnotationTestCase $this->assertEquals('my annotation', $content['text']); $this->assertEquals('my quote', $content['quote']); + /** @var Annotation $annotation */ $annotation = $this->client->getContainer() ->get('doctrine.orm.entity_manager') ->getRepository('WallabagAnnotationBundle:Annotation') @@ -60,20 +106,35 @@ class AnnotationControllerTest extends WallabagAnnotationTestCase $this->assertEquals('my annotation', $annotation->getText()); } - public function testEditAnnotation() + /** + * Test editing an existing annotation. + * + * @dataProvider dataForEachAnnotations + */ + public function testEditAnnotation($prefixUrl) { - $annotation = $this->client->getContainer() - ->get('doctrine.orm.entity_manager') - ->getRepository('WallabagAnnotationBundle:Annotation') - ->findOneByUsername('admin'); + $em = $this->client->getContainer()->get('doctrine.orm.entity_manager'); - $this->logInAs('admin'); + $user = $em + ->getRepository('WallabagUserBundle:User') + ->findOneByUserName('admin'); + $entry = $em + ->getRepository('WallabagCoreBundle:Entry') + ->findOneByUsernameAndNotArchived('admin'); + + $annotation = new Annotation($user); + $annotation->setEntry($entry); + $annotation->setText('This is my annotation /o/'); + $annotation->setQuote('my quote'); + + $em->persist($annotation); + $em->flush(); $headers = ['CONTENT_TYPE' => 'application/json']; $content = json_encode([ 'text' => 'a modified annotation', ]); - $crawler = $this->client->request('PUT', 'annotations/'.$annotation->getId().'.json', [], [], $headers, $content); + $this->client->request('PUT', $prefixUrl.'/'.$annotation->getId().'.json', [], [], $headers, $content); $this->assertEquals(200, $this->client->getResponse()->getStatusCode()); $content = json_decode($this->client->getResponse()->getContent(), true); @@ -83,35 +144,56 @@ class AnnotationControllerTest extends WallabagAnnotationTestCase $this->assertEquals('a modified annotation', $content['text']); $this->assertEquals('my quote', $content['quote']); - $annotationUpdated = $this->client->getContainer() - ->get('doctrine.orm.entity_manager') + /** @var Annotation $annotationUpdated */ + $annotationUpdated = $em ->getRepository('WallabagAnnotationBundle:Annotation') ->findOneById($annotation->getId()); $this->assertEquals('a modified annotation', $annotationUpdated->getText()); + + $em->remove($annotationUpdated); + $em->flush(); } - public function testDeleteAnnotation() + /** + * Test deleting an annotation. + * + * @dataProvider dataForEachAnnotations + */ + public function testDeleteAnnotation($prefixUrl) { - $annotation = $this->client->getContainer() - ->get('doctrine.orm.entity_manager') - ->getRepository('WallabagAnnotationBundle:Annotation') - ->findOneByUsername('admin'); + $em = $this->client->getContainer()->get('doctrine.orm.entity_manager'); - $this->logInAs('admin'); + $user = $em + ->getRepository('WallabagUserBundle:User') + ->findOneByUserName('admin'); + $entry = $em + ->getRepository('WallabagCoreBundle:Entry') + ->findOneByUsernameAndNotArchived('admin'); + + $annotation = new Annotation($user); + $annotation->setEntry($entry); + $annotation->setText('This is my annotation /o/'); + $annotation->setQuote('my quote'); + + $em->persist($annotation); + $em->flush(); + + if ('annotations' === $prefixUrl) { + $this->logInAs('admin'); + } $headers = ['CONTENT_TYPE' => 'application/json']; $content = json_encode([ 'text' => 'a modified annotation', ]); - $crawler = $this->client->request('DELETE', 'annotations/'.$annotation->getId().'.json', [], [], $headers, $content); + $this->client->request('DELETE', $prefixUrl.'/'.$annotation->getId().'.json', [], [], $headers, $content); $this->assertEquals(200, $this->client->getResponse()->getStatusCode()); $content = json_decode($this->client->getResponse()->getContent(), true); - $this->assertEquals('a modified annotation', $content['text']); + $this->assertEquals('This is my annotation /o/', $content['text']); - $annotationDeleted = $this->client->getContainer() - ->get('doctrine.orm.entity_manager') + $annotationDeleted = $em ->getRepository('WallabagAnnotationBundle:Annotation') ->findOneById($annotation->getId()); 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; abstract class WallabagAnnotationTestCase extends WebTestCase { /** - * @var Client + * @var \Symfony\Bundle\FrameworkBundle\Client */ protected $client = null; @@ -35,7 +35,7 @@ abstract class WallabagAnnotationTestCase extends WebTestCase } /** - * @return Client + * @return \Symfony\Bundle\FrameworkBundle\Client */ protected function createAuthorizedClient() { @@ -49,7 +49,7 @@ abstract class WallabagAnnotationTestCase extends WebTestCase $firewallName = $container->getParameter('fos_user.firewall_name'); $this->user = $userManager->findUserBy(['username' => 'admin']); - $loginManager->loginUser($firewallName, $this->user); + $loginManager->logInUser($firewallName, $this->user); // save the login token into the session and put it in a cookie $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 $this->assertEquals($entry->getUserEmail(), $content['user_email']); $this->assertEquals($entry->getUserId(), $content['user_id']); - $this->assertTrue( - $this->client->getResponse()->headers->contains( - 'Content-Type', - 'application/json' - ) - ); + $this->assertEquals('application/json', $this->client->getResponse()->headers->get('Content-Type')); + } + + public function testExportEntry() + { + $entry = $this->client->getContainer() + ->get('doctrine.orm.entity_manager') + ->getRepository('WallabagCoreBundle:Entry') + ->findOneBy(['user' => 1, 'isArchived' => false]); + + if (!$entry) { + $this->markTestSkipped('No content found in db.'); + } + + $this->client->request('GET', '/api/entries/'.$entry->getId().'/export.epub'); + $this->assertEquals(200, $this->client->getResponse()->getStatusCode()); + + // epub format got the content type in the content + $this->assertContains('application/epub', $this->client->getResponse()->getContent()); + $this->assertEquals('application/epub+zip', $this->client->getResponse()->headers->get('Content-Type')); + + // re-auth client for mobi + $client = $this->createAuthorizedClient(); + $client->request('GET', '/api/entries/'.$entry->getId().'/export.mobi'); + $this->assertEquals(200, $client->getResponse()->getStatusCode()); + + $this->assertEquals('application/x-mobipocket-ebook', $client->getResponse()->headers->get('Content-Type')); + + // re-auth client for pdf + $client = $this->createAuthorizedClient(); + $client->request('GET', '/api/entries/'.$entry->getId().'/export.pdf'); + $this->assertEquals(200, $client->getResponse()->getStatusCode()); + + $this->assertContains('PDF-', $client->getResponse()->getContent()); + $this->assertEquals('application/pdf', $client->getResponse()->headers->get('Content-Type')); + + // re-auth client for pdf + $client = $this->createAuthorizedClient(); + $client->request('GET', '/api/entries/'.$entry->getId().'/export.txt'); + $this->assertEquals(200, $client->getResponse()->getStatusCode()); + + $this->assertContains('text/plain', $client->getResponse()->headers->get('Content-Type')); + + // re-auth client for pdf + $client = $this->createAuthorizedClient(); + $client->request('GET', '/api/entries/'.$entry->getId().'/export.csv'); + $this->assertEquals(200, $client->getResponse()->getStatusCode()); + + $this->assertContains('application/csv', $client->getResponse()->headers->get('Content-Type')); } public function testGetOneEntryWrongUser() @@ -70,12 +113,7 @@ class WallabagRestControllerTest extends WallabagApiTestCase $this->assertEquals(1, $content['page']); $this->assertGreaterThanOrEqual(1, $content['pages']); - $this->assertTrue( - $this->client->getResponse()->headers->contains( - 'Content-Type', - 'application/json' - ) - ); + $this->assertEquals('application/json', $this->client->getResponse()->headers->get('Content-Type')); } public function testGetEntriesWithFullOptions() @@ -117,12 +155,7 @@ class WallabagRestControllerTest extends WallabagApiTestCase $this->assertContains('since=1443274283', $content['_links'][$link]['href']); } - $this->assertTrue( - $this->client->getResponse()->headers->contains( - 'Content-Type', - 'application/json' - ) - ); + $this->assertEquals('application/json', $this->client->getResponse()->headers->get('Content-Type')); } public function testGetStarredEntries() @@ -150,12 +183,7 @@ class WallabagRestControllerTest extends WallabagApiTestCase $this->assertContains('sort=updated', $content['_links'][$link]['href']); } - $this->assertTrue( - $this->client->getResponse()->headers->contains( - 'Content-Type', - 'application/json' - ) - ); + $this->assertEquals('application/json', $this->client->getResponse()->headers->get('Content-Type')); } public function testGetArchiveEntries() @@ -182,12 +210,7 @@ class WallabagRestControllerTest extends WallabagApiTestCase $this->assertContains('archive=1', $content['_links'][$link]['href']); } - $this->assertTrue( - $this->client->getResponse()->headers->contains( - 'Content-Type', - 'application/json' - ) - ); + $this->assertEquals('application/json', $this->client->getResponse()->headers->get('Content-Type')); } public function testGetTaggedEntries() @@ -214,12 +237,7 @@ class WallabagRestControllerTest extends WallabagApiTestCase $this->assertContains('tags='.urlencode('foo,bar'), $content['_links'][$link]['href']); } - $this->assertTrue( - $this->client->getResponse()->headers->contains( - 'Content-Type', - 'application/json' - ) - ); + $this->assertEquals('application/json', $this->client->getResponse()->headers->get('Content-Type')); } public function testGetDatedEntries() @@ -246,12 +264,7 @@ class WallabagRestControllerTest extends WallabagApiTestCase $this->assertContains('since=1443274283', $content['_links'][$link]['href']); } - $this->assertTrue( - $this->client->getResponse()->headers->contains( - 'Content-Type', - 'application/json' - ) - ); + $this->assertEquals('application/json', $this->client->getResponse()->headers->get('Content-Type')); } public function testGetDatedSupEntries() @@ -279,12 +292,7 @@ class WallabagRestControllerTest extends WallabagApiTestCase $this->assertContains('since='.($future->getTimestamp() + 1000), $content['_links'][$link]['href']); } - $this->assertTrue( - $this->client->getResponse()->headers->contains( - 'Content-Type', - 'application/json' - ) - ); + $this->assertEquals('application/json', $this->client->getResponse()->headers->get('Content-Type')); } 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 @@ 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 { @@ -570,4 +575,264 @@ class ConfigControllerTest extends WallabagCoreTestCase $config->set('demo_mode_enabled', 0); $config->set('demo_mode_username', 'wallabag'); } + + public function testDeleteUserButtonVisibility() + { + $this->logInAs('admin'); + $client = $this->getClient(); + + $crawler = $client->request('GET', '/config'); + + $this->assertGreaterThan(1, $body = $crawler->filter('body')->extract(['_text'])); + $this->assertContains('config.form_user.delete.button', $body[0]); + + $em = $client->getContainer()->get('doctrine.orm.entity_manager'); + + $user = $em + ->getRepository('WallabagUserBundle:User') + ->findOneByUsername('empty'); + $user->setExpired(1); + $em->persist($user); + + $user = $em + ->getRepository('WallabagUserBundle:User') + ->findOneByUsername('bob'); + $user->setExpired(1); + $em->persist($user); + + $em->flush(); + + $crawler = $client->request('GET', '/config'); + + $this->assertGreaterThan(1, $body = $crawler->filter('body')->extract(['_text'])); + $this->assertNotContains('config.form_user.delete.button', $body[0]); + + $client->request('GET', '/account/delete'); + $this->assertEquals(403, $client->getResponse()->getStatusCode()); + + $user = $em + ->getRepository('WallabagUserBundle:User') + ->findOneByUsername('empty'); + $user->setExpired(0); + $em->persist($user); + + $user = $em + ->getRepository('WallabagUserBundle:User') + ->findOneByUsername('bob'); + $user->setExpired(0); + $em->persist($user); + + $em->flush(); + } + + public function testDeleteAccount() + { + $client = $this->getClient(); + $em = $client->getContainer()->get('doctrine.orm.entity_manager'); + + $user = new User(); + $user->setName('Wallace'); + $user->setEmail('wallace@wallabag.org'); + $user->setUsername('wallace'); + $user->setPlainPassword('wallace'); + $user->setEnabled(true); + $user->addRole('ROLE_SUPER_ADMIN'); + + $em->persist($user); + + $config = new Config($user); + + $config->setTheme('material'); + $config->setItemsPerPage(30); + $config->setReadingSpeed(1); + $config->setLanguage('en'); + $config->setPocketConsumerKey('xxxxx'); + + $em->persist($config); + $em->flush(); + + $this->logInAs('wallace'); + $loggedInUserId = $this->getLoggedInUserId(); + + // create entry to check after user deletion + // that this entry is also deleted + $crawler = $client->request('GET', '/new'); + + $this->assertEquals(200, $client->getResponse()->getStatusCode()); + + $form = $crawler->filter('form[name=entry]')->form(); + $data = [ + 'entry[url]' => $url = 'https://github.com/wallabag/wallabag', + ]; + + $client->submit($form, $data); + $this->assertEquals(302, $client->getResponse()->getStatusCode()); + + $crawler = $client->request('GET', '/config'); + + $deleteLink = $crawler->filter('.delete-account')->last()->link(); + + $client->click($deleteLink); + $this->assertEquals(302, $client->getResponse()->getStatusCode()); + + $em = $client->getContainer()->get('doctrine.orm.entity_manager'); + $user = $em + ->getRepository('WallabagUserBundle:User') + ->createQueryBuilder('u') + ->where('u.username = :username')->setParameter('username', 'wallace') + ->getQuery() + ->getOneOrNullResult() + ; + + $this->assertNull($user); + + $entries = $client->getContainer() + ->get('doctrine.orm.entity_manager') + ->getRepository('WallabagCoreBundle:Entry') + ->findByUser($loggedInUserId); + + $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'); + } }