From 3d4b0b306456aa7e5f16d9dda9043a74438f320f Mon Sep 17 00:00:00 2001 From: Pascal MARTIN Date: Tue, 4 Oct 2016 13:57:03 +0200 Subject: Routing: epub format is allowed for API --- src/Wallabag/ApiBundle/Resources/config/routing_rest.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Wallabag/ApiBundle/Resources/config/routing_rest.yml b/src/Wallabag/ApiBundle/Resources/config/routing_rest.yml index 5f43f971..9aef7e8e 100644 --- a/src/Wallabag/ApiBundle/Resources/config/routing_rest.yml +++ b/src/Wallabag/ApiBundle/Resources/config/routing_rest.yml @@ -2,3 +2,5 @@ entries: type: rest resource: "WallabagApiBundle:WallabagRest" name_prefix: api_ + requirements: + _format: xml|json|html|epub -- cgit v1.2.3 From 24de86653440de3f84d585f089caab3734dcb573 Mon Sep 17 00:00:00 2001 From: Pascal MARTIN Date: Tue, 4 Oct 2016 13:57:18 +0200 Subject: API: getEntry can return EPUB --- src/Wallabag/ApiBundle/Controller/WallabagRestController.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Wallabag/ApiBundle/Controller/WallabagRestController.php b/src/Wallabag/ApiBundle/Controller/WallabagRestController.php index a0d9d4f3..072e5f85 100644 --- a/src/Wallabag/ApiBundle/Controller/WallabagRestController.php +++ b/src/Wallabag/ApiBundle/Controller/WallabagRestController.php @@ -127,11 +127,18 @@ class WallabagRestController extends FOSRestController * * @return JsonResponse */ - public function getEntryAction(Entry $entry) + public function getEntryAction(Entry $entry, $_format) { $this->validateAuthentication(); $this->validateUserAccess($entry->getUser()->getId()); + if ($_format === 'epub') { + return $this->get('wallabag_core.helper.entries_export') + ->setEntries($entry) + ->updateTitle('entry') + ->exportAs($_format); + } + $json = $this->get('serializer')->serialize($entry, 'json'); return (new JsonResponse())->setJson($json); -- cgit v1.2.3 From 3f3a60879e168f4a8040c441e295fa63e024961d Mon Sep 17 00:00:00 2001 From: Jeremy Benoist Date: Sat, 8 Oct 2016 12:59:17 +0200 Subject: Add entry export in API Export isn't available for json & xml because user can use the default entry endpoint instead. --- app/config/config.yml | 2 + .../Controller/WallabagRestController.php | 38 ++++++-- .../ApiBundle/Resources/config/routing_rest.yml | 10 +- .../Controller/WallabagRestControllerTest.php | 104 +++++++++++---------- 4 files changed, 90 insertions(+), 64 deletions(-) diff --git a/app/config/config.yml b/app/config/config.yml index 75d7299c..48f317c7 100644 --- a/app/config/config.yml +++ b/app/config/config.yml @@ -130,6 +130,8 @@ fos_rest: nelmio_api_doc: sandbox: enabled: false + cache: + enabled: true name: wallabag API documentation nelmio_cors: diff --git a/src/Wallabag/ApiBundle/Controller/WallabagRestController.php b/src/Wallabag/ApiBundle/Controller/WallabagRestController.php index 072e5f85..85875589 100644 --- a/src/Wallabag/ApiBundle/Controller/WallabagRestController.php +++ b/src/Wallabag/ApiBundle/Controller/WallabagRestController.php @@ -3,7 +3,7 @@ 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 Symfony\Component\HttpFoundation\Request; @@ -12,6 +12,7 @@ use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Security\Core\Exception\AccessDeniedException; use Wallabag\CoreBundle\Entity\Entry; use Wallabag\CoreBundle\Entity\Tag; +use FOS\RestBundle\Controller\Annotations\Route; class WallabagRestController extends FOSRestController { @@ -95,7 +96,7 @@ class WallabagRestController extends FOSRestController $pagerfantaFactory = new PagerfantaFactory('page', 'perPage'); $paginatedCollection = $pagerfantaFactory->createRepresentation( $pager, - new Route( + new HateoasRoute( 'api_get_entries', [ 'archive' => $isArchived, @@ -127,23 +128,40 @@ class WallabagRestController extends FOSRestController * * @return JsonResponse */ - public function getEntryAction(Entry $entry, $_format) + public function getEntryAction(Entry $entry) { $this->validateAuthentication(); $this->validateUserAccess($entry->getUser()->getId()); - if ($_format === 'epub') { - return $this->get('wallabag_core.helper.entries_export') - ->setEntries($entry) - ->updateTitle('entry') - ->exportAs($_format); - } - $json = $this->get('serializer')->serialize($entry, 'json'); 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"} + * } + * ) + * + * @Route(requirements={"_format"="epub|mobi|pdf|txt|csv"}) + * + * @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. * diff --git a/src/Wallabag/ApiBundle/Resources/config/routing_rest.yml b/src/Wallabag/ApiBundle/Resources/config/routing_rest.yml index 9aef7e8e..35f8b2c1 100644 --- a/src/Wallabag/ApiBundle/Resources/config/routing_rest.yml +++ b/src/Wallabag/ApiBundle/Resources/config/routing_rest.yml @@ -1,6 +1,4 @@ -entries: - type: rest - resource: "WallabagApiBundle:WallabagRest" - name_prefix: api_ - requirements: - _format: xml|json|html|epub +api: + type: rest + resource: "WallabagApiBundle:WallabagRest" + name_prefix: api_ diff --git a/tests/Wallabag/ApiBundle/Controller/WallabagRestControllerTest.php b/tests/Wallabag/ApiBundle/Controller/WallabagRestControllerTest.php index 65b65290..79353857 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() -- cgit v1.2.3 From e4b46f77ef2984b33f4dff0efea0585c7ab0cfbf Mon Sep 17 00:00:00 2001 From: Thomas Citharel Date: Sun, 26 Jun 2016 13:36:53 +0200 Subject: work --- .../CoreBundle/Controller/ConfigController.php | 20 ++++++++++++++++++++ .../Resources/translations/messages.da.yml | 3 +++ .../Resources/translations/messages.de.yml | 3 +++ .../Resources/translations/messages.en.yml | 3 +++ .../Resources/translations/messages.es.yml | 3 +++ .../Resources/translations/messages.fa.yml | 3 +++ .../Resources/translations/messages.fr.yml | 3 +++ .../Resources/translations/messages.it.yml | 3 +++ .../Resources/translations/messages.oc.yml | 3 +++ .../Resources/translations/messages.pl.yml | 3 +++ .../Resources/translations/messages.ro.yml | 3 +++ .../Resources/translations/messages.tr.yml | 3 +++ .../views/themes/material/Config/index.html.twig | 1 + src/Wallabag/UserBundle/Entity/User.php | 12 +++++++++++- 14 files changed, 65 insertions(+), 1 deletion(-) diff --git a/src/Wallabag/CoreBundle/Controller/ConfigController.php b/src/Wallabag/CoreBundle/Controller/ConfigController.php index 91cdcae5..0a306d57 100644 --- a/src/Wallabag/CoreBundle/Controller/ConfigController.php +++ b/src/Wallabag/CoreBundle/Controller/ConfigController.php @@ -251,4 +251,24 @@ class ConfigController extends Controller return $config; } + + /** + * Delete account for current user. + * + * @Route("/account/delete", name="delete_account") + * + * @return \Symfony\Component\HttpFoundation\RedirectResponse + */ + public function deleteAccountAction() + { + $em = $this->get('fos_user.user_manager'); + $em->deleteUser($this->getUser()); + + $this->get('session')->getFlashBag()->add( + 'notice', + 'flashes.account.notice.account_deleted' + ); + + return $this->redirect($this->generateUrl('fos_user_security_logout')); + } } diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.da.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.da.yml index 2652a102..69bfe7b3 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.da.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.da.yml @@ -486,3 +486,6 @@ flashes: notice: # client_created: 'New client created.' # client_deleted: 'Client deleted' + account: + notice: + # account_deleted: 'Account deleted' diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.de.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.de.yml index e0f29b61..f4d13442 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.de.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.de.yml @@ -486,3 +486,6 @@ flashes: notice: client_created: 'Neuer Client erstellt.' client_deleted: 'Client gelöscht' + account: + notice: + # account_deleted: 'Account deleted' diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.en.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.en.yml index b8e98112..6ddb38a0 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.en.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.en.yml @@ -485,6 +485,9 @@ flashes: notice: client_created: 'New client %name% created.' client_deleted: 'Client %name% deleted' + account: + notice: + account_deleted: 'Account deleted' user: notice: added: 'User "%username%" added' diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.es.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.es.yml index 70633bd7..05b03938 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.es.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.es.yml @@ -486,3 +486,6 @@ flashes: notice: client_created: 'Nuevo cliente creado.' client_deleted: 'Cliente suprimido' + account: + notice: + # account_deleted: 'Account deleted' diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml index 074ab7a8..3a01a8ed 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml @@ -485,3 +485,6 @@ flashes: notice: # client_created: 'New client created.' # client_deleted: 'Client deleted' + account: + notice: + # account_deleted: 'Account deleted' diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml index 6d85a5ae..09445836 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml @@ -486,3 +486,6 @@ flashes: notice: client_created: 'Nouveau client %name% créé' client_deleted: 'Client %name% supprimé' + account: + notice: + account_deleted: 'Compte supprimé' diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.it.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.it.yml index 15f7e774..93a11aee 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.it.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.it.yml @@ -486,3 +486,6 @@ flashes: notice: client_created: 'Nuovo client creato.' client_deleted: 'Client eliminato' + account: + notice: + # account_deleted: 'Account deleted' diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml index 1d10be2a..8cadf9a3 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml @@ -486,3 +486,6 @@ flashes: notice: client_created: 'Novèl client creat' client_deleted: 'Client suprimit' + account: + notice: + # account_deleted: 'Account deleted' diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml index 547e9c8b..d392272f 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml @@ -486,3 +486,6 @@ flashes: notice: client_created: 'Nowy klient utworzony.' client_deleted: 'Klient usunięty' + account: + notice: + # account_deleted: 'Account deleted' diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml index 2b1d4f6d..f2bce442 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml @@ -486,3 +486,6 @@ flashes: notice: # client_created: 'New client created.' # client_deleted: 'Client deleted' + account: + notice: + # account_deleted: 'Account deleted' diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml index 8cfc245a..d2bf0504 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml @@ -485,3 +485,6 @@ flashes: notice: # client_created: 'New client created.' # client_deleted: 'Client deleted' + account: + notice: + # account_deleted: 'Account deleted' 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 270c077f..cebde1ac 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,7 @@ {{ form_widget(form.user.save, {'attr': {'class': 'btn waves-effect waves-light'}}) }} {{ form_widget(form.user._token) }} + {{ 'config.user.delete_account' | trans }}
diff --git a/src/Wallabag/UserBundle/Entity/User.php b/src/Wallabag/UserBundle/Entity/User.php index d98ae76a..eb7774cd 100644 --- a/src/Wallabag/UserBundle/Entity/User.php +++ b/src/Wallabag/UserBundle/Entity/User.php @@ -25,7 +25,7 @@ use Wallabag\CoreBundle\Entity\Entry; * @UniqueEntity("email") * @UniqueEntity("username") */ -class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterface +class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterface, \Serializable { /** * @var int @@ -240,4 +240,14 @@ class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterf return false; } + + public function serialize() + { + return serialize($this->id); + } + + public function unserialize($serialized) + { + $this->id = unserialize($serialized); + } } -- cgit v1.2.3 From abb5291cd5dc98211273e5d3b516a6a0ed8b980f Mon Sep 17 00:00:00 2001 From: Thomas Citharel Date: Fri, 1 Jul 2016 10:58:15 +0200 Subject: CS --- src/Wallabag/CoreBundle/Controller/ConfigController.php | 14 +++++++------- src/Wallabag/UserBundle/Entity/User.php | 12 ++++++------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Wallabag/CoreBundle/Controller/ConfigController.php b/src/Wallabag/CoreBundle/Controller/ConfigController.php index 0a306d57..3cafd1bc 100644 --- a/src/Wallabag/CoreBundle/Controller/ConfigController.php +++ b/src/Wallabag/CoreBundle/Controller/ConfigController.php @@ -252,13 +252,13 @@ class ConfigController extends Controller return $config; } - /** - * Delete account for current user. - * - * @Route("/account/delete", name="delete_account") - * - * @return \Symfony\Component\HttpFoundation\RedirectResponse - */ + /** + * Delete account for current user. + * + * @Route("/account/delete", name="delete_account") + * + * @return \Symfony\Component\HttpFoundation\RedirectResponse + */ public function deleteAccountAction() { $em = $this->get('fos_user.user_manager'); diff --git a/src/Wallabag/UserBundle/Entity/User.php b/src/Wallabag/UserBundle/Entity/User.php index eb7774cd..ae12e5d5 100644 --- a/src/Wallabag/UserBundle/Entity/User.php +++ b/src/Wallabag/UserBundle/Entity/User.php @@ -243,11 +243,11 @@ class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterf public function serialize() { - return serialize($this->id); - } + return serialize($this->id); + } - public function unserialize($serialized) - { - $this->id = unserialize($serialized); - } + public function unserialize($serialized) + { + $this->id = unserialize($serialized); + } } -- cgit v1.2.3 From bb0c78f4a636fcc8f60dd3b42ad733e7f31e0bb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolas=20L=C5=93uillet?= Date: Thu, 8 Sep 2016 14:07:36 +0200 Subject: Added check if there is only one user Added translations and documentation --- docs/en/user/configuration.rst | 2 ++ docs/fr/user/configuration.rst | 2 ++ src/Wallabag/CoreBundle/Controller/ConfigController.php | 14 ++++++++++++++ .../CoreBundle/Resources/translations/messages.da.yml | 1 + .../CoreBundle/Resources/translations/messages.de.yml | 1 + .../CoreBundle/Resources/translations/messages.en.yml | 1 + .../CoreBundle/Resources/translations/messages.es.yml | 1 + .../CoreBundle/Resources/translations/messages.fa.yml | 1 + .../CoreBundle/Resources/translations/messages.fr.yml | 1 + .../CoreBundle/Resources/translations/messages.it.yml | 1 + .../CoreBundle/Resources/translations/messages.oc.yml | 1 + .../CoreBundle/Resources/translations/messages.pl.yml | 1 + .../CoreBundle/Resources/translations/messages.ro.yml | 1 + .../CoreBundle/Resources/translations/messages.tr.yml | 1 + .../Resources/views/themes/baggy/Config/index.html.twig | 3 +++ .../Resources/views/themes/material/Config/index.html.twig | 5 ++++- src/Wallabag/UserBundle/Repository/UserRepository.php | 14 ++++++++++++++ 17 files changed, 50 insertions(+), 1 deletion(-) diff --git a/docs/en/user/configuration.rst b/docs/en/user/configuration.rst index f4c55dea..824878dc 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/fr/user/configuration.rst b/docs/fr/user/configuration.rst index 278f0022..2654e8ad 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/CoreBundle/Controller/ConfigController.php b/src/Wallabag/CoreBundle/Controller/ConfigController.php index 3cafd1bc..70a641f7 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(), ]); } @@ -257,10 +261,20 @@ class ConfigController extends Controller * * @Route("/account/delete", name="delete_account") * + * @throws AccessDeniedHttpException + * * @return \Symfony\Component\HttpFoundation\RedirectResponse */ public function deleteAccountAction() { + $enabledUsers = $this->getDoctrine() + ->getRepository('WallabagUserBundle:User') + ->getSumEnabledUsers(); + + if ($enabledUsers <= 1) { + throw new AccessDeniedHttpException(); + } + $em = $this->get('fos_user.user_manager'); $em->deleteUser($this->getUser()); diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.da.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.da.yml index 69bfe7b3..4c412592 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.da.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.da.yml @@ -88,6 +88,7 @@ config: name_label: 'Navn' email_label: 'Emailadresse' # twoFactorAuthentication_label: 'Two factor authentication' + # delete_account: 'Delete my account' form_password: old_password_label: 'Gammel adgangskode' new_password_label: 'Ny adgangskode' diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.de.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.de.yml index f4d13442..99b79bce 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.de.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.de.yml @@ -88,6 +88,7 @@ config: name_label: 'Name' email_label: 'E-Mail-Adresse' twoFactorAuthentication_label: 'Zwei-Faktor-Authentifizierung' + # delete_account: 'Delete my account' form_password: old_password_label: 'Altes Kennwort' new_password_label: 'Neues Kennwort' diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.en.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.en.yml index 6ddb38a0..94144ed4 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.en.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.en.yml @@ -88,6 +88,7 @@ config: name_label: 'Name' email_label: 'Email' twoFactorAuthentication_label: 'Two factor authentication' + delete_account: 'Delete my account' form_password: old_password_label: 'Current password' new_password_label: 'New password' diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.es.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.es.yml index 05b03938..a5e8d722 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.es.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.es.yml @@ -88,6 +88,7 @@ config: name_label: 'Nombre' email_label: 'Direccion e-mail' twoFactorAuthentication_label: 'Autentificación de dos factores' + # delete_account: 'Delete my account' form_password: old_password_label: 'Contraseña actual' new_password_label: 'Nueva contraseña' diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml index 3a01a8ed..4b8d9689 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml @@ -88,6 +88,7 @@ config: name_label: 'نام' email_label: 'نشانی ایمیل' twoFactorAuthentication_label: 'تأیید ۲مرحله‌ای' + # delete_account: 'Delete my account' form_password: old_password_label: 'رمز قدیمی' new_password_label: 'رمز تازه' diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml index 09445836..67cd5f0e 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml @@ -88,6 +88,7 @@ config: name_label: 'Nom' email_label: 'Adresse e-mail' twoFactorAuthentication_label: 'Double authentification' + delete_account: 'Supprimer mon compte' form_password: old_password_label: 'Mot de passe actuel' new_password_label: 'Nouveau mot de passe' diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.it.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.it.yml index 93a11aee..55d961f3 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.it.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.it.yml @@ -88,6 +88,7 @@ config: name_label: 'Nome' email_label: 'E-mail' twoFactorAuthentication_label: 'Two factor authentication' + # delete_account: 'Delete my account' form_password: old_password_label: 'Password corrente' new_password_label: 'Nuova password' diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml index 8cadf9a3..5c6b4247 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml @@ -88,6 +88,7 @@ config: name_label: 'Nom' email_label: 'Adreça de corrièl' twoFactorAuthentication_label: 'Dobla autentificacion' + # delete_account: 'Delete my account' form_password: old_password_label: 'Senhal actual' new_password_label: 'Senhal novèl' diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml index d392272f..be966427 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml @@ -88,6 +88,7 @@ config: name_label: 'Nazwa' email_label: 'Adres email' twoFactorAuthentication_label: 'Autoryzacja dwuetapowa' + # delete_account: 'Delete my account' form_password: old_password_label: 'Stare hasło' new_password_label: 'Nowe hasło' diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml index f2bce442..1e52cf0b 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml @@ -88,6 +88,7 @@ config: name_label: 'Nume' email_label: 'E-mail' # twoFactorAuthentication_label: 'Two factor authentication' + # delete_account: 'Delete my account' form_password: old_password_label: 'Parola veche' new_password_label: 'Parola nouă' diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml index d2bf0504..ef5477e1 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml @@ -88,6 +88,7 @@ config: name_label: 'İsim' email_label: 'E-posta' twoFactorAuthentication_label: 'İki adımlı doğrulama' + # delete_account: 'Delete my account' form_password: old_password_label: 'Eski şifre' new_password_label: 'Yeni şifre' 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..29575272 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 @@ -148,6 +148,9 @@ {{ form_widget(form.user._token) }} {{ form_widget(form.user.save) }} + {% if enabled_users > 1 %} + {{ 'config.form_user.delete_account' | trans }} + {% endif %}

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

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 cebde1ac..5aa3eabe 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,7 +167,10 @@ {{ form_widget(form.user.save, {'attr': {'class': 'btn waves-effect waves-light'}}) }} {{ form_widget(form.user._token) }} - {{ 'config.user.delete_account' | trans }} + + {% if enabled_users > 1 %} + {{ 'config.form_user.delete_account' | trans }} + {% endif %}
diff --git a/src/Wallabag/UserBundle/Repository/UserRepository.php b/src/Wallabag/UserBundle/Repository/UserRepository.php index 009c4881..178761e6 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 = 0') + ->getQuery() + ->getSingleScalarResult(); + } } -- cgit v1.2.3 From 821bb8768507a16a28e7f98143aba5218691c18b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolas=20L=C5=93uillet?= Date: Thu, 8 Sep 2016 15:47:03 +0200 Subject: Added tests --- .../CoreBundle/Controller/ConfigControllerTest.php | 85 ++++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/tests/Wallabag/CoreBundle/Controller/ConfigControllerTest.php b/tests/Wallabag/CoreBundle/Controller/ConfigControllerTest.php index 1954c654..23e7e786 100644 --- a/tests/Wallabag/CoreBundle/Controller/ConfigControllerTest.php +++ b/tests/Wallabag/CoreBundle/Controller/ConfigControllerTest.php @@ -570,4 +570,89 @@ 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_account', $body[0]); + + $em = $client->getContainer()->get('doctrine.orm.entity_manager'); + + $user = $em + ->getRepository('WallabagUserBundle:User') + ->findOneByUsername('wallace'); + $user->setExpired(1); + $em->persist($user); + + $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_account', $body[0]); + + $client->request('GET', '/account/delete'); + $this->assertEquals(403, $client->getResponse()->getStatusCode()); + + $user = $em + ->getRepository('WallabagUserBundle:User') + ->findOneByUsername('wallace'); + $user->setExpired(0); + $em->persist($user); + + $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() + { + $this->logInAs('wallace'); + $client = $this->getClient(); + + $crawler = $client->request('GET', '/config'); + + $deleteLink = $crawler->filter('.red')->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->assertTrue(false !== $user); + } } -- cgit v1.2.3 From 71254701b7e5364516b7510e9237c00c678dac1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolas=20L=C5=93uillet?= Date: Thu, 8 Sep 2016 16:00:39 +0200 Subject: Changed tests --- .../CoreBundle/Resources/views/themes/baggy/Config/index.html.twig | 2 +- .../CoreBundle/Resources/views/themes/material/Config/index.html.twig | 2 +- tests/Wallabag/CoreBundle/Controller/ConfigControllerTest.php | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) 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 29575272..b10db473 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 @@ -149,7 +149,7 @@ {{ form_widget(form.user._token) }} {{ form_widget(form.user.save) }} {% if enabled_users > 1 %} - {{ 'config.form_user.delete_account' | trans }} + {% endif %} 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 5aa3eabe..a8a3f9dc 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 @@ -169,7 +169,7 @@ {% if enabled_users > 1 %} - {{ 'config.form_user.delete_account' | trans }} + {% endif %}
diff --git a/tests/Wallabag/CoreBundle/Controller/ConfigControllerTest.php b/tests/Wallabag/CoreBundle/Controller/ConfigControllerTest.php index 23e7e786..282bf570 100644 --- a/tests/Wallabag/CoreBundle/Controller/ConfigControllerTest.php +++ b/tests/Wallabag/CoreBundle/Controller/ConfigControllerTest.php @@ -639,7 +639,7 @@ class ConfigControllerTest extends WallabagCoreTestCase $crawler = $client->request('GET', '/config'); - $deleteLink = $crawler->filter('.red')->last()->link(); + $deleteLink = $crawler->filter('.delete-account')->last()->link(); $client->click($deleteLink); $this->assertEquals(302, $client->getResponse()->getStatusCode()); @@ -653,6 +653,6 @@ class ConfigControllerTest extends WallabagCoreTestCase ->getOneOrNullResult() ; - $this->assertTrue(false !== $user); + $this->assertNull($user); } } -- cgit v1.2.3 From b84026871169cee8cdd4aba892c128beee98e91a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolas=20L=C5=93uillet?= Date: Fri, 9 Sep 2016 10:08:12 +0200 Subject: Added a test to check if entries are also deleted --- .../CoreBundle/Controller/ConfigControllerTest.php | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/Wallabag/CoreBundle/Controller/ConfigControllerTest.php b/tests/Wallabag/CoreBundle/Controller/ConfigControllerTest.php index 282bf570..75a5e308 100644 --- a/tests/Wallabag/CoreBundle/Controller/ConfigControllerTest.php +++ b/tests/Wallabag/CoreBundle/Controller/ConfigControllerTest.php @@ -637,6 +637,20 @@ class ConfigControllerTest extends WallabagCoreTestCase $this->logInAs('wallace'); $client = $this->getClient(); + // 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(); @@ -654,5 +668,12 @@ class ConfigControllerTest extends WallabagCoreTestCase ; $this->assertNull($user); + + $entries = $client->getContainer() + ->get('doctrine.orm.entity_manager') + ->getRepository('WallabagCoreBundle:Entry') + ->findByUser($this->getLoggedInUserId()); + + $this->assertEmpty($entries); } } -- cgit v1.2.3 From c3396c65ef8532091614ec9ae298ba124d58b2b9 Mon Sep 17 00:00:00 2001 From: Jeremy Benoist Date: Sat, 8 Oct 2016 14:07:13 +0200 Subject: Fix some tests --- .../CoreBundle/Controller/ConfigControllerTest.php | 39 ++++++++++++++-------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/tests/Wallabag/CoreBundle/Controller/ConfigControllerTest.php b/tests/Wallabag/CoreBundle/Controller/ConfigControllerTest.php index 75a5e308..7929b63d 100644 --- a/tests/Wallabag/CoreBundle/Controller/ConfigControllerTest.php +++ b/tests/Wallabag/CoreBundle/Controller/ConfigControllerTest.php @@ -3,6 +3,8 @@ namespace Tests\Wallabag\CoreBundle\Controller; use Tests\Wallabag\CoreBundle\WallabagCoreTestCase; +use Wallabag\CoreBundle\Entity\Config; +use Wallabag\UserBundle\Entity\User; class ConfigControllerTest extends WallabagCoreTestCase { @@ -583,12 +585,6 @@ class ConfigControllerTest extends WallabagCoreTestCase $em = $client->getContainer()->get('doctrine.orm.entity_manager'); - $user = $em - ->getRepository('WallabagUserBundle:User') - ->findOneByUsername('wallace'); - $user->setExpired(1); - $em->persist($user); - $user = $em ->getRepository('WallabagUserBundle:User') ->findOneByUsername('empty'); @@ -611,12 +607,6 @@ class ConfigControllerTest extends WallabagCoreTestCase $client->request('GET', '/account/delete'); $this->assertEquals(403, $client->getResponse()->getStatusCode()); - $user = $em - ->getRepository('WallabagUserBundle:User') - ->findOneByUsername('wallace'); - $user->setExpired(0); - $em->persist($user); - $user = $em ->getRepository('WallabagUserBundle:User') ->findOneByUsername('empty'); @@ -634,8 +624,31 @@ class ConfigControllerTest extends WallabagCoreTestCase public function testDeleteAccount() { - $this->logInAs('wallace'); $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'); // create entry to check after user deletion // that this entry is also deleted -- cgit v1.2.3 From eed812afd0626697d33f7e9d3bfd8eca138c463d Mon Sep 17 00:00:00 2001 From: Jeremy Benoist Date: Sat, 8 Oct 2016 19:39:12 +0200 Subject: Logout user before deleting it And add a smal description --- .../CoreBundle/Controller/ConfigController.php | 43 ++++++++++++---------- .../views/themes/material/Config/index.html.twig | 10 ++++- 2 files changed, 32 insertions(+), 21 deletions(-) diff --git a/src/Wallabag/CoreBundle/Controller/ConfigController.php b/src/Wallabag/CoreBundle/Controller/ConfigController.php index 70a641f7..662da2a0 100644 --- a/src/Wallabag/CoreBundle/Controller/ConfigController.php +++ b/src/Wallabag/CoreBundle/Controller/ConfigController.php @@ -261,28 +261,31 @@ class ConfigController extends Controller * * @Route("/account/delete", name="delete_account") * + * @param Request $request + * * @throws AccessDeniedHttpException * * @return \Symfony\Component\HttpFoundation\RedirectResponse */ - public function deleteAccountAction() - { - $enabledUsers = $this->getDoctrine() - ->getRepository('WallabagUserBundle:User') - ->getSumEnabledUsers(); - - if ($enabledUsers <= 1) { - throw new AccessDeniedHttpException(); - } - - $em = $this->get('fos_user.user_manager'); - $em->deleteUser($this->getUser()); - - $this->get('session')->getFlashBag()->add( - 'notice', - 'flashes.account.notice.account_deleted' - ); - - return $this->redirect($this->generateUrl('fos_user_security_logout')); - } + 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/Resources/views/themes/material/Config/index.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/material/Config/index.html.twig index a8a3f9dc..25d259b8 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 @@ -169,7 +169,15 @@ {% if enabled_users > 1 %} - +


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

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

+ +
{% endif %} -- cgit v1.2.3 From 876d77a67d1415c1be56f08e9a2d3b1d294bb61f Mon Sep 17 00:00:00 2001 From: Jeremy Benoist Date: Sat, 8 Oct 2016 19:39:50 +0200 Subject: Better display and description MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Confirmation message isn’t required since it is written in the delete description --- .../CoreBundle/Resources/translations/messages.da.yml | 9 +++++---- .../CoreBundle/Resources/translations/messages.de.yml | 9 +++++---- .../CoreBundle/Resources/translations/messages.en.yml | 9 +++++---- .../CoreBundle/Resources/translations/messages.es.yml | 9 +++++---- .../CoreBundle/Resources/translations/messages.fa.yml | 9 +++++---- .../CoreBundle/Resources/translations/messages.fr.yml | 9 +++++---- .../CoreBundle/Resources/translations/messages.it.yml | 9 +++++---- .../CoreBundle/Resources/translations/messages.oc.yml | 9 +++++---- .../CoreBundle/Resources/translations/messages.pl.yml | 9 +++++---- .../CoreBundle/Resources/translations/messages.ro.yml | 9 +++++---- .../CoreBundle/Resources/translations/messages.tr.yml | 9 +++++---- .../Resources/views/themes/baggy/Config/index.html.twig | 12 +++++++++--- .../Resources/views/themes/material/Config/index.html.twig | 8 ++++---- .../Wallabag/CoreBundle/Controller/ConfigControllerTest.php | 7 ++++--- 14 files changed, 72 insertions(+), 54 deletions(-) diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.da.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.da.yml index 4c412592..2de5d7bd 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.da.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.da.yml @@ -88,7 +88,11 @@ config: name_label: 'Navn' email_label: 'Emailadresse' # twoFactorAuthentication_label: 'Two factor authentication' - # delete_account: 'Delete my account' + delete: + # title: Delete my account (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) + # button: Delete my account form_password: old_password_label: 'Gammel adgangskode' new_password_label: 'Ny adgangskode' @@ -487,6 +491,3 @@ flashes: notice: # client_created: 'New client created.' # client_deleted: 'Client deleted' - account: - notice: - # account_deleted: 'Account deleted' diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.de.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.de.yml index 99b79bce..515d43a0 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.de.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.de.yml @@ -88,7 +88,11 @@ config: name_label: 'Name' email_label: 'E-Mail-Adresse' twoFactorAuthentication_label: 'Zwei-Faktor-Authentifizierung' - # delete_account: 'Delete my account' + delete: + # title: Delete my account (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) + # button: Delete my account form_password: old_password_label: 'Altes Kennwort' new_password_label: 'Neues Kennwort' @@ -487,6 +491,3 @@ flashes: notice: client_created: 'Neuer Client erstellt.' client_deleted: 'Client gelöscht' - account: - notice: - # account_deleted: 'Account deleted' diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.en.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.en.yml index 94144ed4..43f5a950 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.en.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.en.yml @@ -88,7 +88,11 @@ config: name_label: 'Name' email_label: 'Email' twoFactorAuthentication_label: 'Two factor authentication' - delete_account: 'Delete my account' + delete: + title: Delete my account (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) + button: Delete my account form_password: old_password_label: 'Current password' new_password_label: 'New password' @@ -486,9 +490,6 @@ flashes: notice: client_created: 'New client %name% created.' client_deleted: 'Client %name% deleted' - account: - notice: - account_deleted: 'Account deleted' user: notice: added: 'User "%username%" added' diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.es.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.es.yml index a5e8d722..adeab2b0 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.es.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.es.yml @@ -88,7 +88,11 @@ config: name_label: 'Nombre' email_label: 'Direccion e-mail' twoFactorAuthentication_label: 'Autentificación de dos factores' - # delete_account: 'Delete my account' + delete: + # title: Delete my account (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) + # button: Delete my account form_password: old_password_label: 'Contraseña actual' new_password_label: 'Nueva contraseña' @@ -487,6 +491,3 @@ flashes: notice: client_created: 'Nuevo cliente creado.' client_deleted: 'Cliente suprimido' - account: - notice: - # account_deleted: 'Account deleted' diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml index 4b8d9689..0751752b 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml @@ -88,7 +88,11 @@ config: name_label: 'نام' email_label: 'نشانی ایمیل' twoFactorAuthentication_label: 'تأیید ۲مرحله‌ای' - # delete_account: 'Delete my account' + delete: + # title: Delete my account (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) + # button: Delete my account form_password: old_password_label: 'رمز قدیمی' new_password_label: 'رمز تازه' @@ -486,6 +490,3 @@ flashes: notice: # client_created: 'New client created.' # client_deleted: 'Client deleted' - account: - notice: - # account_deleted: 'Account deleted' diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml index 67cd5f0e..1c32a77c 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml @@ -88,7 +88,11 @@ config: name_label: 'Nom' email_label: 'Adresse e-mail' twoFactorAuthentication_label: 'Double authentification' - delete_account: 'Supprimer mon compte' + 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' form_password: old_password_label: 'Mot de passe actuel' new_password_label: 'Nouveau mot de passe' @@ -487,6 +491,3 @@ flashes: notice: client_created: 'Nouveau client %name% créé' client_deleted: 'Client %name% supprimé' - account: - notice: - account_deleted: 'Compte supprimé' diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.it.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.it.yml index 55d961f3..f662bd55 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.it.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.it.yml @@ -88,7 +88,11 @@ config: name_label: 'Nome' email_label: 'E-mail' twoFactorAuthentication_label: 'Two factor authentication' - # delete_account: 'Delete my account' + delete: + # title: Delete my account (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) + # button: Delete my account form_password: old_password_label: 'Password corrente' new_password_label: 'Nuova password' @@ -487,6 +491,3 @@ flashes: notice: client_created: 'Nuovo client creato.' client_deleted: 'Client eliminato' - account: - notice: - # account_deleted: 'Account deleted' diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml index 5c6b4247..9e314f73 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml @@ -88,7 +88,11 @@ config: name_label: 'Nom' email_label: 'Adreça de corrièl' twoFactorAuthentication_label: 'Dobla autentificacion' - # delete_account: 'Delete my account' + delete: + # title: Delete my account (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) + # button: Delete my account form_password: old_password_label: 'Senhal actual' new_password_label: 'Senhal novèl' @@ -487,6 +491,3 @@ flashes: notice: client_created: 'Novèl client creat' client_deleted: 'Client suprimit' - account: - notice: - # account_deleted: 'Account deleted' diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml index be966427..9877d59a 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml @@ -88,7 +88,11 @@ config: name_label: 'Nazwa' email_label: 'Adres email' twoFactorAuthentication_label: 'Autoryzacja dwuetapowa' - # delete_account: 'Delete my account' + delete: + # title: Delete my account (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) + # button: Delete my account form_password: old_password_label: 'Stare hasło' new_password_label: 'Nowe hasło' @@ -487,6 +491,3 @@ flashes: notice: client_created: 'Nowy klient utworzony.' client_deleted: 'Klient usunięty' - account: - notice: - # account_deleted: 'Account deleted' diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml index 1e52cf0b..83246ed3 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml @@ -88,7 +88,11 @@ config: name_label: 'Nume' email_label: 'E-mail' # twoFactorAuthentication_label: 'Two factor authentication' - # delete_account: 'Delete my account' + delete: + # title: Delete my account (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) + # button: Delete my account form_password: old_password_label: 'Parola veche' new_password_label: 'Parola nouă' @@ -487,6 +491,3 @@ flashes: notice: # client_created: 'New client created.' # client_deleted: 'Client deleted' - account: - notice: - # account_deleted: 'Account deleted' diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml index ef5477e1..24dd6ff8 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml @@ -88,7 +88,11 @@ config: name_label: 'İsim' email_label: 'E-posta' twoFactorAuthentication_label: 'İki adımlı doğrulama' - # delete_account: 'Delete my account' + delete: + # title: Delete my account (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) + # button: Delete my account form_password: old_password_label: 'Eski şifre' new_password_label: 'Yeni şifre' @@ -486,6 +490,3 @@ flashes: notice: # client_created: 'New client created.' # client_deleted: 'Client deleted' - account: - notice: - # account_deleted: 'Account deleted' 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 b10db473..54508b6d 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 @@ -148,11 +148,17 @@ {{ form_widget(form.user._token) }} {{ form_widget(form.user.save) }} - {% if enabled_users > 1 %} - - {% endif %} + {% if enabled_users > 1 %} +

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

+ +

{{ 'config.form_user.delete.description'|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 25d259b8..8434508d 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 @@ -172,10 +172,10 @@


{% endif %} diff --git a/tests/Wallabag/CoreBundle/Controller/ConfigControllerTest.php b/tests/Wallabag/CoreBundle/Controller/ConfigControllerTest.php index 7929b63d..5faa0130 100644 --- a/tests/Wallabag/CoreBundle/Controller/ConfigControllerTest.php +++ b/tests/Wallabag/CoreBundle/Controller/ConfigControllerTest.php @@ -581,7 +581,7 @@ class ConfigControllerTest extends WallabagCoreTestCase $crawler = $client->request('GET', '/config'); $this->assertGreaterThan(1, $body = $crawler->filter('body')->extract(['_text'])); - $this->assertContains('config.form_user.delete_account', $body[0]); + $this->assertContains('config.form_user.delete.button', $body[0]); $em = $client->getContainer()->get('doctrine.orm.entity_manager'); @@ -602,7 +602,7 @@ class ConfigControllerTest extends WallabagCoreTestCase $crawler = $client->request('GET', '/config'); $this->assertGreaterThan(1, $body = $crawler->filter('body')->extract(['_text'])); - $this->assertNotContains('config.form_user.delete_account', $body[0]); + $this->assertNotContains('config.form_user.delete.button', $body[0]); $client->request('GET', '/account/delete'); $this->assertEquals(403, $client->getResponse()->getStatusCode()); @@ -649,6 +649,7 @@ class ConfigControllerTest extends WallabagCoreTestCase $em->flush(); $this->logInAs('wallace'); + $loggedInUserId = $this->getLoggedInUserId(); // create entry to check after user deletion // that this entry is also deleted @@ -685,7 +686,7 @@ class ConfigControllerTest extends WallabagCoreTestCase $entries = $client->getContainer() ->get('doctrine.orm.entity_manager') ->getRepository('WallabagCoreBundle:Entry') - ->findByUser($this->getLoggedInUserId()); + ->findByUser($loggedInUserId); $this->assertEmpty($entries); } -- cgit v1.2.3 From 3f604467564e86623513107161587f7e34d4f369 Mon Sep 17 00:00:00 2001 From: Jeremy Benoist Date: Sat, 8 Oct 2016 20:26:06 +0200 Subject: Fix PostgreSQL query MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PostgreSQL doesn’t like when we compare interger and boolean :) --- src/Wallabag/UserBundle/Repository/UserRepository.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Wallabag/UserBundle/Repository/UserRepository.php b/src/Wallabag/UserBundle/Repository/UserRepository.php index 178761e6..445edb3c 100644 --- a/src/Wallabag/UserBundle/Repository/UserRepository.php +++ b/src/Wallabag/UserBundle/Repository/UserRepository.php @@ -48,7 +48,7 @@ class UserRepository extends EntityRepository { return $this->createQueryBuilder('u') ->select('count(u)') - ->andWhere('u.expired = 0') + ->andWhere('u.expired = false') ->getQuery() ->getSingleScalarResult(); } -- cgit v1.2.3 From a730cae384e7c83fe35c5ee0210204dede9b1678 Mon Sep 17 00:00:00 2001 From: Jeremy Benoist Date: Sat, 8 Oct 2016 20:33:21 +0200 Subject: Bonus: display driver in install command --- src/Wallabag/CoreBundle/Command/InstallCommand.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Wallabag/CoreBundle/Command/InstallCommand.php b/src/Wallabag/CoreBundle/Command/InstallCommand.php index cc7c2c94..42982e4a 100644 --- a/src/Wallabag/CoreBundle/Command/InstallCommand.php +++ b/src/Wallabag/CoreBundle/Command/InstallCommand.php @@ -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'; -- cgit v1.2.3 From 7ac3e575f1285a07987346461cee9445ac8c0b3b Mon Sep 17 00:00:00 2001 From: Jeremy Benoist Date: Sat, 8 Oct 2016 20:35:16 +0200 Subject: CS --- .../CoreBundle/Controller/ConfigController.php | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Wallabag/CoreBundle/Controller/ConfigController.php b/src/Wallabag/CoreBundle/Controller/ConfigController.php index 662da2a0..abd35c02 100644 --- a/src/Wallabag/CoreBundle/Controller/ConfigController.php +++ b/src/Wallabag/CoreBundle/Controller/ConfigController.php @@ -256,17 +256,17 @@ 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 - */ + /** + * 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() -- cgit v1.2.3 From 9810f30821105f6340b64e8bdca9f91b9da8a6ba Mon Sep 17 00:00:00 2001 From: Jeremy Benoist Date: Sat, 8 Oct 2016 21:16:40 +0200 Subject: Remove unnecessary user serialization --- src/Wallabag/UserBundle/Entity/User.php | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/Wallabag/UserBundle/Entity/User.php b/src/Wallabag/UserBundle/Entity/User.php index ae12e5d5..d98ae76a 100644 --- a/src/Wallabag/UserBundle/Entity/User.php +++ b/src/Wallabag/UserBundle/Entity/User.php @@ -25,7 +25,7 @@ use Wallabag\CoreBundle\Entity\Entry; * @UniqueEntity("email") * @UniqueEntity("username") */ -class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterface, \Serializable +class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterface { /** * @var int @@ -240,14 +240,4 @@ class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterf return false; } - - public function serialize() - { - return serialize($this->id); - } - - public function unserialize($serialized) - { - $this->id = unserialize($serialized); - } } -- cgit v1.2.3 From dd248e45618aaa1f4dbaa62e17645034555615d3 Mon Sep 17 00:00:00 2001 From: Quent-in Date: Sun, 9 Oct 2016 18:24:42 +0200 Subject: Occitan version update If you need to write dates in full letter you might be interessed in https://github.com/fightbulc/moment.php It comes with lots of languages ;) Q: where are the "previous" and "next" bouton text? Can't find them to translate them. --- .../Resources/translations/messages.oc.yml | 77 +++++++++++----------- 1 file changed, 38 insertions(+), 39 deletions(-) diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml index 1d10be2a..f9cae5bb 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' @@ -96,7 +96,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 +209,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 +265,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 +286,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 +298,13 @@ 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' 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 +389,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 +397,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 +405,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 +418,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' flashes: config: @@ -455,7 +454,7 @@ 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' entry: notice: @@ -467,12 +466,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" -- cgit v1.2.3 From c26d1285bab7b90f300062d9c805d1bad107490f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolas=20L=C5=93uillet?= Date: Mon, 10 Oct 2016 10:05:50 +0200 Subject: Fixed review --- src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml index f9cae5bb..d19de664 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml @@ -46,7 +46,7 @@ footer: social: 'Social' powered_by: 'propulsat per' about: 'A prepaus' - stats: "Dempuèi %user_creation% avètz legit %nb_archives% articles. Es a l'entorn de %per_day% per jorn !" + stats: "Dempuèi %user_creation% avètz legit %nb_archives% articles. Es a l'entorn de %per_day% per jorn !" config: page_title: 'Configuracion' @@ -301,6 +301,7 @@ quickstart: 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' docs: title: 'Documentacion complèta' description: "I a un fum de fonccionalitats dins wallabag. Esitetz pas a legir lo manual per las conéisser e aprendre a las utilizar." -- cgit v1.2.3 From a9e4d6dad29a28de56460301705e08cb1866ab2d Mon Sep 17 00:00:00 2001 From: Quent-in Date: Mon, 10 Oct 2016 20:01:25 +0200 Subject: Update messages.oc.yml I thought I had translated the Docker installation line, apparently not! Thanks Nicolas for the other changes! --- src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml index d19de664..bdcf877d 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml @@ -301,7 +301,7 @@ quickstart: 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: "I a un fum de fonccionalitats dins wallabag. Esitetz pas a legir lo manual per las conéisser e aprendre a las utilizar." -- cgit v1.2.3 From fa100dd1e0c39502014319121bbbcbc7831cab27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Rumi=C5=84ski?= Date: Wed, 12 Oct 2016 19:32:30 +0200 Subject: Update messages.pl.yml translate delete section to polish --- src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml index a2fe13db..6f22f90d 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml @@ -89,10 +89,10 @@ config: email_label: 'Adres email' twoFactorAuthentication_label: 'Autoryzacja dwuetapowa' delete: - # title: Delete my account (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) - # button: Delete my account + 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 form_password: old_password_label: 'Stare hasło' new_password_label: 'Nowe hasło' -- cgit v1.2.3 From f1c3f68e909e51f071f3f4b2ef9430163bbfd7a4 Mon Sep 17 00:00:00 2001 From: Jeremy Benoist Date: Sat, 15 Oct 2016 16:46:42 +0200 Subject: ApiDoc & Route annotation were conflicted MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The generated error was “Unable to guess how to get a Doctrine instance from the request information.”. I haven’t checked deeper in Doctrine (I know it was coming from the DoctrineParamConverter). Anyway, I check for FosRest possiblity to add extra format without allowing them for every route (like it was done in the first place). I finally found a way but it then seems all request goes to the FormatListener of FosRest so I needed to add a custom rules to match all request to be sure we don’t get a 406 error from FosRest. Should be ok now … --- app/config/config.yml | 27 ++++++++++++++++++++-- app/config/routing_rest.yml | 5 ++-- composer.json | 4 ++-- .../Controller/WallabagRestController.php | 2 -- 4 files changed, 29 insertions(+), 9 deletions(-) diff --git a/app/config/config.yml b/app/config/config.yml index 2f102c45..b4760073 100644 --- a/app/config/config.yml +++ b/app/config/config.yml @@ -112,12 +112,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: @@ -126,6 +140,15 @@ 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: 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/composer.json b/composer.json index 79de337b..4f7ad291 100644 --- a/composer.json +++ b/composer.json @@ -54,8 +54,8 @@ "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", diff --git a/src/Wallabag/ApiBundle/Controller/WallabagRestController.php b/src/Wallabag/ApiBundle/Controller/WallabagRestController.php index fa573988..96f75807 100644 --- a/src/Wallabag/ApiBundle/Controller/WallabagRestController.php +++ b/src/Wallabag/ApiBundle/Controller/WallabagRestController.php @@ -167,8 +167,6 @@ class WallabagRestController extends FOSRestController * } * ) * - * @Route(requirements={"_format"="epub|mobi|pdf|txt|csv"}) - * * @return Response */ public function getEntryExportAction(Entry $entry, Request $request) -- cgit v1.2.3 From 5960aa1ffc8ee07850d4865a8df7440750af4b2b Mon Sep 17 00:00:00 2001 From: Jeremy Benoist Date: Sat, 15 Oct 2016 18:00:08 +0200 Subject: CS --- src/Wallabag/ApiBundle/Controller/WallabagRestController.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Wallabag/ApiBundle/Controller/WallabagRestController.php b/src/Wallabag/ApiBundle/Controller/WallabagRestController.php index 96f75807..3437bb9b 100644 --- a/src/Wallabag/ApiBundle/Controller/WallabagRestController.php +++ b/src/Wallabag/ApiBundle/Controller/WallabagRestController.php @@ -12,7 +12,6 @@ use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Security\Core\Exception\AccessDeniedException; use Wallabag\CoreBundle\Entity\Entry; use Wallabag\CoreBundle\Entity\Tag; -use FOS\RestBundle\Controller\Annotations\Route; class WallabagRestController extends FOSRestController { -- cgit v1.2.3 From 351eb8d97ea1520f87ae762faf297083a716c945 Mon Sep 17 00:00:00 2001 From: Thomas Citharel Date: Fri, 18 Mar 2016 16:41:23 +0100 Subject: bring annotations to API --- .../Controller/WallabagAnnotationController.php | 28 +---- .../Controller/WallabagRestController.php | 132 +++++++++++++++++++++ 2 files changed, 138 insertions(+), 22 deletions(-) diff --git a/src/Wallabag/AnnotationBundle/Controller/WallabagAnnotationController.php b/src/Wallabag/AnnotationBundle/Controller/WallabagAnnotationController.php index ad083e31..80ac0035 100644 --- a/src/Wallabag/AnnotationBundle/Controller/WallabagAnnotationController.php +++ b/src/Wallabag/AnnotationBundle/Controller/WallabagAnnotationController.php @@ -15,11 +15,9 @@ 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 */ @@ -42,13 +40,7 @@ class WallabagAnnotationController extends FOSRestController * * @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"=""}, - * } - * ) + * @see Wallabag\ApiBundle\Controller\WallabagRestController * * @return Response */ @@ -81,11 +73,7 @@ class WallabagAnnotationController extends FOSRestController /** * 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") * @@ -110,11 +98,7 @@ class WallabagAnnotationController extends FOSRestController /** * 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") * diff --git a/src/Wallabag/ApiBundle/Controller/WallabagRestController.php b/src/Wallabag/ApiBundle/Controller/WallabagRestController.php index 3437bb9b..6275afa0 100644 --- a/src/Wallabag/ApiBundle/Controller/WallabagRestController.php +++ b/src/Wallabag/ApiBundle/Controller/WallabagRestController.php @@ -6,12 +6,14 @@ use FOS\RestBundle\Controller\FOSRestController; 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 { @@ -517,6 +519,136 @@ 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"} + * } + * ) + * + * @return Response + */ + public function getAnnotationsAction(Entry $entry) + { + + $this->validateAuthentication(); + + $annotationRows = $this + ->getDoctrine() + ->getRepository('WallabagAnnotationBundle:Annotation') + ->findAnnotationsByPageId($entry->getId(), $this->getUser()->getId()); + $total = count($annotationRows); + $annotations = array('total' => $total, 'rows' => $annotationRows); + + $json = $this->get('serializer')->serialize($annotations, 'json'); + + return $this->renderJsonResponse($json); + } + + /** + * Creates a new annotation. + * + * @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 Response + */ + public function postAnnotationAction(Request $request, Entry $entry) + { + $this->validateAuthentication(); + + $data = json_decode($request->getContent(), true); + + $em = $this->getDoctrine()->getManager(); + + $annotation = new Annotation($this->getUser()); + + $annotation->setText($data['text']); + if (array_key_exists('quote', $data)) { + $annotation->setQuote($data['quote']); + } + if (array_key_exists('ranges', $data)) { + $annotation->setRanges($data['ranges']); + } + + $annotation->setEntry($entry); + + $em->persist($annotation); + $em->flush(); + + $json = $this->get('serializer')->serialize($annotation, 'json'); + + return $this->renderJsonResponse($json); + } + + /** + * Updates an annotation. + * + * @ApiDoc( + * requirements={ + * {"name"="annotation", "dataType"="string", "requirement"="\w+", "description"="The annotation ID"} + * } + * ) + * + * @ParamConverter("annotation", class="WallabagAnnotationBundle:Annotation") + * + * @return Response + */ + public function putAnnotationAction(Annotation $annotation, Request $request) + { + + $this->validateAuthentication(); + + $data = json_decode($request->getContent(), true); + + if (!is_null($data['text'])) { + $annotation->setText($data['text']); + } + + $em = $this->getDoctrine()->getManager(); + $em->flush(); + + $json = $this->get('serializer')->serialize($annotation, 'json'); + + return $this->renderJsonResponse($json); + } + + /** + * Removes an annotation. + * + * @ApiDoc( + * requirements={ + * {"name"="annotation", "dataType"="string", "requirement"="\w+", "description"="The annotation ID"} + * } + * ) + * + * @ParamConverter("annotation", class="WallabagAnnotationBundle:Annotation") + * + * @return Response + */ + public function deleteAnnotationAction(Annotation $annotation) + { + + $this->validateAuthentication(); + + $em = $this->getDoctrine()->getManager(); + $em->remove($annotation); + $em->flush(); + + $json = $this->get('serializer')->serialize($annotation, 'json'); + + return $this->renderJsonResponse($json); + } + /** * Retrieve version number. * -- cgit v1.2.3 From c7935f32d2cd7fd29dbc7b07c8f206f6e8b64fb4 Mon Sep 17 00:00:00 2001 From: Thomas Citharel Date: Fri, 18 Mar 2016 22:23:49 +0100 Subject: cs --- .../AnnotationBundle/Controller/WallabagAnnotationController.php | 1 - src/Wallabag/ApiBundle/Controller/WallabagRestController.php | 3 --- 2 files changed, 4 deletions(-) diff --git a/src/Wallabag/AnnotationBundle/Controller/WallabagAnnotationController.php b/src/Wallabag/AnnotationBundle/Controller/WallabagAnnotationController.php index 80ac0035..4143c1b6 100644 --- a/src/Wallabag/AnnotationBundle/Controller/WallabagAnnotationController.php +++ b/src/Wallabag/AnnotationBundle/Controller/WallabagAnnotationController.php @@ -3,7 +3,6 @@ namespace Wallabag\AnnotationBundle\Controller; use FOS\RestBundle\Controller\FOSRestController; -use Nelmio\ApiDocBundle\Annotation\ApiDoc; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; diff --git a/src/Wallabag/ApiBundle/Controller/WallabagRestController.php b/src/Wallabag/ApiBundle/Controller/WallabagRestController.php index 6275afa0..07d0e1e9 100644 --- a/src/Wallabag/ApiBundle/Controller/WallabagRestController.php +++ b/src/Wallabag/ApiBundle/Controller/WallabagRestController.php @@ -532,7 +532,6 @@ class WallabagRestController extends FOSRestController */ public function getAnnotationsAction(Entry $entry) { - $this->validateAuthentication(); $annotationRows = $this @@ -605,7 +604,6 @@ class WallabagRestController extends FOSRestController */ public function putAnnotationAction(Annotation $annotation, Request $request) { - $this->validateAuthentication(); $data = json_decode($request->getContent(), true); @@ -637,7 +635,6 @@ class WallabagRestController extends FOSRestController */ public function deleteAnnotationAction(Annotation $annotation) { - $this->validateAuthentication(); $em = $this->getDoctrine()->getManager(); -- cgit v1.2.3 From 1eea248bb0ebf49772878557211817905a4d6952 Mon Sep 17 00:00:00 2001 From: Thomas Citharel Date: Wed, 5 Oct 2016 19:16:57 +0200 Subject: move code --- .../Controller/WallabagAnnotationController.php | 43 +++++------ .../Controller/WallabagRestController.php | 87 ++++++++-------------- 2 files changed, 48 insertions(+), 82 deletions(-) diff --git a/src/Wallabag/AnnotationBundle/Controller/WallabagAnnotationController.php b/src/Wallabag/AnnotationBundle/Controller/WallabagAnnotationController.php index 4143c1b6..b04c0bc2 100644 --- a/src/Wallabag/AnnotationBundle/Controller/WallabagAnnotationController.php +++ b/src/Wallabag/AnnotationBundle/Controller/WallabagAnnotationController.php @@ -3,6 +3,7 @@ namespace Wallabag\AnnotationBundle\Controller; use FOS\RestBundle\Controller\FOSRestController; +use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; @@ -18,30 +19,30 @@ class WallabagAnnotationController extends FOSRestController * * @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]; + $annotations = array('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 Request $request * @param Entry $entry - * + * @return JsonResponse * @see Wallabag\ApiBundle\Controller\WallabagRestController * - * @return Response */ public function postAnnotationAction(Request $request, Entry $entry) { @@ -66,7 +67,7 @@ class WallabagAnnotationController extends FOSRestController $json = $this->get('serializer')->serialize($annotation, 'json'); - return $this->renderJsonResponse($json); + return (new JsonResponse())->setJson($json); } /** @@ -76,7 +77,9 @@ class WallabagAnnotationController extends FOSRestController * * @ParamConverter("annotation", class="WallabagAnnotationBundle:Annotation") * - * @return Response + * @param Annotation $annotation + * @param Request $request + * @return JsonResponse */ public function putAnnotationAction(Annotation $annotation, Request $request) { @@ -91,7 +94,7 @@ class WallabagAnnotationController extends FOSRestController $json = $this->get('serializer')->serialize($annotation, 'json'); - return $this->renderJsonResponse($json); + return (new JsonResponse())->setJson($json); } /** @@ -101,7 +104,8 @@ class WallabagAnnotationController extends FOSRestController * * @ParamConverter("annotation", class="WallabagAnnotationBundle:Annotation") * - * @return Response + * @param Annotation $annotation + * @return JsonResponse */ public function deleteAnnotationAction(Annotation $annotation) { @@ -111,19 +115,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/ApiBundle/Controller/WallabagRestController.php b/src/Wallabag/ApiBundle/Controller/WallabagRestController.php index 07d0e1e9..e2e4924b 100644 --- a/src/Wallabag/ApiBundle/Controller/WallabagRestController.php +++ b/src/Wallabag/ApiBundle/Controller/WallabagRestController.php @@ -528,29 +528,26 @@ class WallabagRestController extends FOSRestController * } * ) * - * @return Response + * @param Entry $entry + * @return JsonResponse */ public function getAnnotationsAction(Entry $entry) { $this->validateAuthentication(); - $annotationRows = $this - ->getDoctrine() - ->getRepository('WallabagAnnotationBundle:Annotation') - ->findAnnotationsByPageId($entry->getId(), $this->getUser()->getId()); - $total = count($annotationRows); - $annotations = array('total' => $total, 'rows' => $annotationRows); - - $json = $this->get('serializer')->serialize($annotations, 'json'); - - return $this->renderJsonResponse($json); + $response = $this->forward('WallabagApiBundle:WallabagRest:getAnnotations', + [ + 'entry' => $entry + ]); + return $response; } /** * Creates a new annotation. * + * @param Request $request * @param Entry $entry - * + * @return JsonResponse * @ApiDoc( * requirements={ * {"name"="ranges", "dataType"="array", "requirement"="\w+", "description"="The range array for the annotation"}, @@ -559,34 +556,17 @@ class WallabagRestController extends FOSRestController * } * ) * - * @return Response */ public function postAnnotationAction(Request $request, Entry $entry) { $this->validateAuthentication(); - $data = json_decode($request->getContent(), true); - - $em = $this->getDoctrine()->getManager(); - - $annotation = new Annotation($this->getUser()); - - $annotation->setText($data['text']); - if (array_key_exists('quote', $data)) { - $annotation->setQuote($data['quote']); - } - if (array_key_exists('ranges', $data)) { - $annotation->setRanges($data['ranges']); - } - - $annotation->setEntry($entry); - - $em->persist($annotation); - $em->flush(); - - $json = $this->get('serializer')->serialize($annotation, 'json'); - - return $this->renderJsonResponse($json); + $response = $this->forward('WallabagApiBundle:WallabagRest:postAnnotation', + [ + 'request' => $request, + 'entry' => $entry + ]); + return $response; } /** @@ -600,24 +580,20 @@ class WallabagRestController extends FOSRestController * * @ParamConverter("annotation", class="WallabagAnnotationBundle:Annotation") * - * @return Response + * @param Annotation $annotation + * @param Request $request + * @return JsonResponse */ public function putAnnotationAction(Annotation $annotation, Request $request) { $this->validateAuthentication(); - $data = json_decode($request->getContent(), true); - - if (!is_null($data['text'])) { - $annotation->setText($data['text']); - } - - $em = $this->getDoctrine()->getManager(); - $em->flush(); - - $json = $this->get('serializer')->serialize($annotation, 'json'); - - return $this->renderJsonResponse($json); + $response = $this->forward('WallabagApiBundle:WallabagRest:putAnnotation', + [ + 'annotation' => $annotation, + 'request' => $request + ]); + return $response; } /** @@ -631,19 +607,18 @@ class WallabagRestController extends FOSRestController * * @ParamConverter("annotation", class="WallabagAnnotationBundle:Annotation") * - * @return Response + * @param Annotation $annotation + * @return JsonResponse */ public function deleteAnnotationAction(Annotation $annotation) { $this->validateAuthentication(); - $em = $this->getDoctrine()->getManager(); - $em->remove($annotation); - $em->flush(); - - $json = $this->get('serializer')->serialize($annotation, 'json'); - - return $this->renderJsonResponse($json); + $response = $this->forward('WallabagApiBundle:WallabagRest:deleteAnnotation', + [ + 'annotation' => $annotation, + ]); + return $response; } /** -- cgit v1.2.3 From b1e92f8c14d3d506f2bfab628ba8ed95de0e8b51 Mon Sep 17 00:00:00 2001 From: Thomas Citharel Date: Thu, 6 Oct 2016 11:40:14 +0200 Subject: cs --- .../Controller/WallabagAnnotationController.php | 10 ++++++---- .../ApiBundle/Controller/WallabagRestController.php | 19 +++++++++++++------ 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/Wallabag/AnnotationBundle/Controller/WallabagAnnotationController.php b/src/Wallabag/AnnotationBundle/Controller/WallabagAnnotationController.php index b04c0bc2..519fd2f5 100644 --- a/src/Wallabag/AnnotationBundle/Controller/WallabagAnnotationController.php +++ b/src/Wallabag/AnnotationBundle/Controller/WallabagAnnotationController.php @@ -5,7 +5,6 @@ namespace Wallabag\AnnotationBundle\Controller; use FOS\RestBundle\Controller\FOSRestController; 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; @@ -39,10 +38,11 @@ class WallabagAnnotationController extends FOSRestController * Creates a new annotation. * * @param Request $request - * @param Entry $entry + * @param Entry $entry + * * @return JsonResponse - * @see Wallabag\ApiBundle\Controller\WallabagRestController * + * @see Wallabag\ApiBundle\Controller\WallabagRestController */ public function postAnnotationAction(Request $request, Entry $entry) { @@ -78,7 +78,8 @@ class WallabagAnnotationController extends FOSRestController * @ParamConverter("annotation", class="WallabagAnnotationBundle:Annotation") * * @param Annotation $annotation - * @param Request $request + * @param Request $request + * * @return JsonResponse */ public function putAnnotationAction(Annotation $annotation, Request $request) @@ -105,6 +106,7 @@ class WallabagAnnotationController extends FOSRestController * @ParamConverter("annotation", class="WallabagAnnotationBundle:Annotation") * * @param Annotation $annotation + * * @return JsonResponse */ public function deleteAnnotationAction(Annotation $annotation) diff --git a/src/Wallabag/ApiBundle/Controller/WallabagRestController.php b/src/Wallabag/ApiBundle/Controller/WallabagRestController.php index e2e4924b..b30ab267 100644 --- a/src/Wallabag/ApiBundle/Controller/WallabagRestController.php +++ b/src/Wallabag/ApiBundle/Controller/WallabagRestController.php @@ -529,6 +529,7 @@ class WallabagRestController extends FOSRestController * ) * * @param Entry $entry + * * @return JsonResponse */ public function getAnnotationsAction(Entry $entry) @@ -537,8 +538,9 @@ class WallabagRestController extends FOSRestController $response = $this->forward('WallabagApiBundle:WallabagRest:getAnnotations', [ - 'entry' => $entry + 'entry' => $entry, ]); + return $response; } @@ -546,7 +548,8 @@ class WallabagRestController extends FOSRestController * Creates a new annotation. * * @param Request $request - * @param Entry $entry + * @param Entry $entry + * * @return JsonResponse * @ApiDoc( * requirements={ @@ -555,7 +558,6 @@ class WallabagRestController extends FOSRestController * {"name"="text", "dataType"="string", "required"=true, "description"=""}, * } * ) - * */ public function postAnnotationAction(Request $request, Entry $entry) { @@ -564,8 +566,9 @@ class WallabagRestController extends FOSRestController $response = $this->forward('WallabagApiBundle:WallabagRest:postAnnotation', [ 'request' => $request, - 'entry' => $entry + 'entry' => $entry, ]); + return $response; } @@ -581,7 +584,8 @@ class WallabagRestController extends FOSRestController * @ParamConverter("annotation", class="WallabagAnnotationBundle:Annotation") * * @param Annotation $annotation - * @param Request $request + * @param Request $request + * * @return JsonResponse */ public function putAnnotationAction(Annotation $annotation, Request $request) @@ -591,8 +595,9 @@ class WallabagRestController extends FOSRestController $response = $this->forward('WallabagApiBundle:WallabagRest:putAnnotation', [ 'annotation' => $annotation, - 'request' => $request + 'request' => $request, ]); + return $response; } @@ -608,6 +613,7 @@ class WallabagRestController extends FOSRestController * @ParamConverter("annotation", class="WallabagAnnotationBundle:Annotation") * * @param Annotation $annotation + * * @return JsonResponse */ public function deleteAnnotationAction(Annotation $annotation) @@ -618,6 +624,7 @@ class WallabagRestController extends FOSRestController [ 'annotation' => $annotation, ]); + return $response; } -- cgit v1.2.3 From 0c271b9eb0813162b82c6b3bc38604716398ddd1 Mon Sep 17 00:00:00 2001 From: Thomas Citharel Date: Sun, 9 Oct 2016 14:01:28 +0200 Subject: fix cs and phpdoc --- .../Controller/WallabagAnnotationController.php | 2 +- .../Controller/WallabagRestController.php | 24 +++++-------------- .../Controller/AnnotationControllerTest.php | 28 ++++++++++++++++++---- .../WallabagAnnotationTestCase.php | 6 ++--- 4 files changed, 34 insertions(+), 26 deletions(-) diff --git a/src/Wallabag/AnnotationBundle/Controller/WallabagAnnotationController.php b/src/Wallabag/AnnotationBundle/Controller/WallabagAnnotationController.php index 519fd2f5..c13a034f 100644 --- a/src/Wallabag/AnnotationBundle/Controller/WallabagAnnotationController.php +++ b/src/Wallabag/AnnotationBundle/Controller/WallabagAnnotationController.php @@ -27,7 +27,7 @@ class WallabagAnnotationController extends FOSRestController ->getRepository('WallabagAnnotationBundle:Annotation') ->findAnnotationsByPageId($entry->getId(), $this->getUser()->getId()); $total = count($annotationRows); - $annotations = array('total' => $total, 'rows' => $annotationRows); + $annotations = ['total' => $total, 'rows' => $annotationRows]; $json = $this->get('serializer')->serialize($annotations, 'json'); diff --git a/src/Wallabag/ApiBundle/Controller/WallabagRestController.php b/src/Wallabag/ApiBundle/Controller/WallabagRestController.php index b30ab267..0c709ca0 100644 --- a/src/Wallabag/ApiBundle/Controller/WallabagRestController.php +++ b/src/Wallabag/ApiBundle/Controller/WallabagRestController.php @@ -536,12 +536,9 @@ class WallabagRestController extends FOSRestController { $this->validateAuthentication(); - $response = $this->forward('WallabagApiBundle:WallabagRest:getAnnotations', - [ - 'entry' => $entry, - ]); - - return $response; + return $this->forward('WallabagApiBundle:WallabagRest:getAnnotations', [ + 'entry' => $entry, + ]); } /** @@ -563,13 +560,10 @@ class WallabagRestController extends FOSRestController { $this->validateAuthentication(); - $response = $this->forward('WallabagApiBundle:WallabagRest:postAnnotation', - [ + return $this->forward('WallabagApiBundle:WallabagRest:postAnnotation', [ 'request' => $request, 'entry' => $entry, ]); - - return $response; } /** @@ -592,13 +586,10 @@ class WallabagRestController extends FOSRestController { $this->validateAuthentication(); - $response = $this->forward('WallabagApiBundle:WallabagRest:putAnnotation', - [ + return $this->forward('WallabagApiBundle:WallabagRest:putAnnotation', [ 'annotation' => $annotation, 'request' => $request, ]); - - return $response; } /** @@ -620,12 +611,9 @@ class WallabagRestController extends FOSRestController { $this->validateAuthentication(); - $response = $this->forward('WallabagApiBundle:WallabagRest:deleteAnnotation', - [ + return $this->forward('WallabagApiBundle:WallabagRest:deleteAnnotation', [ 'annotation' => $annotation, ]); - - return $response; } /** diff --git a/tests/Wallabag/AnnotationBundle/Controller/AnnotationControllerTest.php b/tests/Wallabag/AnnotationBundle/Controller/AnnotationControllerTest.php index 70849f74..9b2a6f8d 100644 --- a/tests/Wallabag/AnnotationBundle/Controller/AnnotationControllerTest.php +++ b/tests/Wallabag/AnnotationBundle/Controller/AnnotationControllerTest.php @@ -3,11 +3,17 @@ namespace Tests\AnnotationBundle\Controller; use Tests\Wallabag\AnnotationBundle\WallabagAnnotationTestCase; +use Wallabag\AnnotationBundle\Entity\Annotation; +use Wallabag\CoreBundle\Entity\Entry; class AnnotationControllerTest extends WallabagAnnotationTestCase { + /** + * Test fetching annotations for an entry + */ public function testGetAnnotations() { + /** @var Annotation $annotation */ $annotation = $this->client->getContainer() ->get('doctrine.orm.entity_manager') ->getRepository('WallabagAnnotationBundle:Annotation') @@ -18,7 +24,7 @@ class AnnotationControllerTest extends WallabagAnnotationTestCase } $this->logInAs('admin'); - $crawler = $this->client->request('GET', 'annotations/'.$annotation->getEntry()->getId().'.json'); + $this->client->request('GET', 'annotations/'.$annotation->getEntry()->getId().'.json'); $this->assertEquals(200, $this->client->getResponse()->getStatusCode()); $content = json_decode($this->client->getResponse()->getContent(), true); @@ -26,10 +32,14 @@ class AnnotationControllerTest extends WallabagAnnotationTestCase $this->assertEquals($annotation->getText(), $content['rows'][0]['text']); } + /** + * Test creating an annotation for an entry + */ public function testSetAnnotation() { $this->logInAs('admin'); + /** @var Entry $entry */ $entry = $this->client->getContainer() ->get('doctrine.orm.entity_manager') ->getRepository('WallabagCoreBundle:Entry') @@ -41,7 +51,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', 'annotations/'.$entry->getId().'.json', [], [], $headers, $content); $this->assertEquals(200, $this->client->getResponse()->getStatusCode()); @@ -52,6 +62,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,8 +71,12 @@ class AnnotationControllerTest extends WallabagAnnotationTestCase $this->assertEquals('my annotation', $annotation->getText()); } + /** + * Test editing an existing annotation + */ public function testEditAnnotation() { + /** @var Annotation $annotation */ $annotation = $this->client->getContainer() ->get('doctrine.orm.entity_manager') ->getRepository('WallabagAnnotationBundle:Annotation') @@ -73,7 +88,7 @@ class AnnotationControllerTest extends WallabagAnnotationTestCase $content = json_encode([ 'text' => 'a modified annotation', ]); - $crawler = $this->client->request('PUT', 'annotations/'.$annotation->getId().'.json', [], [], $headers, $content); + $this->client->request('PUT', 'annotations/'.$annotation->getId().'.json', [], [], $headers, $content); $this->assertEquals(200, $this->client->getResponse()->getStatusCode()); $content = json_decode($this->client->getResponse()->getContent(), true); @@ -83,6 +98,7 @@ class AnnotationControllerTest extends WallabagAnnotationTestCase $this->assertEquals('a modified annotation', $content['text']); $this->assertEquals('my quote', $content['quote']); + /** @var Annotation $annotationUpdated */ $annotationUpdated = $this->client->getContainer() ->get('doctrine.orm.entity_manager') ->getRepository('WallabagAnnotationBundle:Annotation') @@ -90,8 +106,12 @@ class AnnotationControllerTest extends WallabagAnnotationTestCase $this->assertEquals('a modified annotation', $annotationUpdated->getText()); } + /** + * Test deleting an annotation + */ public function testDeleteAnnotation() { + /** @var Annotation $annotation */ $annotation = $this->client->getContainer() ->get('doctrine.orm.entity_manager') ->getRepository('WallabagAnnotationBundle:Annotation') @@ -103,7 +123,7 @@ class AnnotationControllerTest extends WallabagAnnotationTestCase $content = json_encode([ 'text' => 'a modified annotation', ]); - $crawler = $this->client->request('DELETE', 'annotations/'.$annotation->getId().'.json', [], [], $headers, $content); + $this->client->request('DELETE', 'annotations/'.$annotation->getId().'.json', [], [], $headers, $content); $this->assertEquals(200, $this->client->getResponse()->getStatusCode()); $content = json_decode($this->client->getResponse()->getContent(), true); 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())); -- cgit v1.2.3 From e5edb6e1277e357e02a7f0efe3f5d0143502d4ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolas=20L=C5=93uillet?= Date: Fri, 14 Oct 2016 14:36:08 +0200 Subject: PHP CS --- .../AnnotationBundle/Controller/AnnotationControllerTest.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/Wallabag/AnnotationBundle/Controller/AnnotationControllerTest.php b/tests/Wallabag/AnnotationBundle/Controller/AnnotationControllerTest.php index 9b2a6f8d..8c23de45 100644 --- a/tests/Wallabag/AnnotationBundle/Controller/AnnotationControllerTest.php +++ b/tests/Wallabag/AnnotationBundle/Controller/AnnotationControllerTest.php @@ -9,7 +9,7 @@ use Wallabag\CoreBundle\Entity\Entry; class AnnotationControllerTest extends WallabagAnnotationTestCase { /** - * Test fetching annotations for an entry + * Test fetching annotations for an entry. */ public function testGetAnnotations() { @@ -33,7 +33,7 @@ class AnnotationControllerTest extends WallabagAnnotationTestCase } /** - * Test creating an annotation for an entry + * Test creating an annotation for an entry. */ public function testSetAnnotation() { @@ -72,7 +72,7 @@ class AnnotationControllerTest extends WallabagAnnotationTestCase } /** - * Test editing an existing annotation + * Test editing an existing annotation. */ public function testEditAnnotation() { @@ -107,7 +107,7 @@ class AnnotationControllerTest extends WallabagAnnotationTestCase } /** - * Test deleting an annotation + * Test deleting an annotation. */ public function testDeleteAnnotation() { -- cgit v1.2.3 From d9b7768dca0049e76fc7b5cf6cc6cba0e2b40f3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolas=20L=C5=93uillet?= Date: Fri, 14 Oct 2016 15:00:42 +0200 Subject: Added a missing namespace --- src/Wallabag/ApiBundle/Controller/WallabagRestController.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Wallabag/ApiBundle/Controller/WallabagRestController.php b/src/Wallabag/ApiBundle/Controller/WallabagRestController.php index 0c709ca0..b6b6c9bf 100644 --- a/src/Wallabag/ApiBundle/Controller/WallabagRestController.php +++ b/src/Wallabag/ApiBundle/Controller/WallabagRestController.php @@ -14,6 +14,7 @@ use Symfony\Component\Security\Core\Exception\AccessDeniedException; use Wallabag\CoreBundle\Entity\Entry; use Wallabag\CoreBundle\Entity\Tag; use Wallabag\AnnotationBundle\Entity\Annotation; +use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; class WallabagRestController extends FOSRestController { -- cgit v1.2.3 From 3199ec4702cec9c10ad0c663bcf6ce799068c9aa Mon Sep 17 00:00:00 2001 From: Jeremy Benoist Date: Sat, 22 Oct 2016 09:17:01 +0200 Subject: CS --- src/Wallabag/ApiBundle/Controller/WallabagRestController.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Wallabag/ApiBundle/Controller/WallabagRestController.php b/src/Wallabag/ApiBundle/Controller/WallabagRestController.php index b6b6c9bf..0c709ca0 100644 --- a/src/Wallabag/ApiBundle/Controller/WallabagRestController.php +++ b/src/Wallabag/ApiBundle/Controller/WallabagRestController.php @@ -14,7 +14,6 @@ use Symfony\Component\Security\Core\Exception\AccessDeniedException; use Wallabag\CoreBundle\Entity\Entry; use Wallabag\CoreBundle\Entity\Tag; use Wallabag\AnnotationBundle\Entity\Annotation; -use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; class WallabagRestController extends FOSRestController { -- cgit v1.2.3 From aa4741091f3f2cc7e4a7ef061a04678597ddeca8 Mon Sep 17 00:00:00 2001 From: Jeremy Benoist Date: Sat, 22 Oct 2016 12:09:20 +0200 Subject: Add test on /api/annotations Fix controller forward in WallabagRestController. Update PHPDoc so it is sorted the same way as others one Duplicate all annotations test to use both api & normal way Also, make annotation tests independent to each other --- .../Repository/AnnotationRepository.php | 6 +- .../Controller/WallabagRestController.php | 33 ++--- .../Controller/AnnotationControllerTest.php | 136 +++++++++++++++------ 3 files changed, 120 insertions(+), 55 deletions(-) diff --git a/src/Wallabag/AnnotationBundle/Repository/AnnotationRepository.php b/src/Wallabag/AnnotationBundle/Repository/AnnotationRepository.php index 5f7da70e..8cccffba 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() ; } diff --git a/src/Wallabag/ApiBundle/Controller/WallabagRestController.php b/src/Wallabag/ApiBundle/Controller/WallabagRestController.php index 0c709ca0..a73d44ca 100644 --- a/src/Wallabag/ApiBundle/Controller/WallabagRestController.php +++ b/src/Wallabag/ApiBundle/Controller/WallabagRestController.php @@ -536,7 +536,7 @@ class WallabagRestController extends FOSRestController { $this->validateAuthentication(); - return $this->forward('WallabagApiBundle:WallabagRest:getAnnotations', [ + return $this->forward('WallabagAnnotationBundle:WallabagAnnotation:getAnnotations', [ 'entry' => $entry, ]); } @@ -544,10 +544,6 @@ class WallabagRestController extends FOSRestController /** * Creates a new annotation. * - * @param Request $request - * @param Entry $entry - * - * @return JsonResponse * @ApiDoc( * requirements={ * {"name"="ranges", "dataType"="array", "requirement"="\w+", "description"="The range array for the annotation"}, @@ -555,15 +551,20 @@ class WallabagRestController extends FOSRestController * {"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('WallabagApiBundle:WallabagRest:postAnnotation', [ - 'request' => $request, - 'entry' => $entry, - ]); + return $this->forward('WallabagAnnotationBundle:WallabagAnnotation:postAnnotation', [ + 'request' => $request, + 'entry' => $entry, + ]); } /** @@ -586,10 +587,10 @@ class WallabagRestController extends FOSRestController { $this->validateAuthentication(); - return $this->forward('WallabagApiBundle:WallabagRest:putAnnotation', [ - 'annotation' => $annotation, - 'request' => $request, - ]); + return $this->forward('WallabagAnnotationBundle:WallabagAnnotation:putAnnotation', [ + 'annotation' => $annotation, + 'request' => $request, + ]); } /** @@ -611,9 +612,9 @@ class WallabagRestController extends FOSRestController { $this->validateAuthentication(); - return $this->forward('WallabagApiBundle:WallabagRest:deleteAnnotation', [ - 'annotation' => $annotation, - ]); + return $this->forward('WallabagAnnotationBundle:WallabagAnnotation:deleteAnnotation', [ + 'annotation' => $annotation, + ]); } /** diff --git a/tests/Wallabag/AnnotationBundle/Controller/AnnotationControllerTest.php b/tests/Wallabag/AnnotationBundle/Controller/AnnotationControllerTest.php index 8c23de45..cee0b847 100644 --- a/tests/Wallabag/AnnotationBundle/Controller/AnnotationControllerTest.php +++ b/tests/Wallabag/AnnotationBundle/Controller/AnnotationControllerTest.php @@ -8,40 +8,75 @@ use Wallabag\CoreBundle\Entity\Entry; class AnnotationControllerTest extends WallabagAnnotationTestCase { + /** + * 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() + { + return [ + ['/api/annotations'], + ['annotations'], + ]; + } + /** * Test fetching annotations for an entry. + * + * @dataProvider dataForEachAnnotations */ - public function testGetAnnotations() + public function testGetAnnotations($prefixUrl) { - /** @var Annotation $annotation */ - $annotation = $this->client->getContainer() - ->get('doctrine.orm.entity_manager') - ->getRepository('WallabagAnnotationBundle:Annotation') - ->findOneByUsername('admin'); + $em = $this->client->getContainer()->get('doctrine.orm.entity_manager'); + + $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('content'); - if (!$annotation) { - $this->markTestSkipped('No content found in db.'); + $em->persist($annotation); + $em->flush(); + + if ('annotations' === $prefixUrl) { + $this->logInAs('admin'); } - $this->logInAs('admin'); - $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(); } /** * Test creating an annotation for an entry. + * + * @dataProvider dataForEachAnnotations */ - public function testSetAnnotation() + public function testSetAnnotation($prefixUrl) { - $this->logInAs('admin'); + $em = $this->client->getContainer()->get('doctrine.orm.entity_manager'); + + if ('annotations' === $prefixUrl) { + $this->logInAs('admin'); + } /** @var Entry $entry */ - $entry = $this->client->getContainer() - ->get('doctrine.orm.entity_manager') + $entry = $em ->getRepository('WallabagCoreBundle:Entry') ->findOneByUsernameAndNotArchived('admin'); @@ -51,7 +86,7 @@ class AnnotationControllerTest extends WallabagAnnotationTestCase 'quote' => 'my quote', 'ranges' => ['start' => '', 'startOffset' => 24, 'end' => '', 'endOffset' => 31], ]); - $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()); @@ -73,22 +108,33 @@ class AnnotationControllerTest extends WallabagAnnotationTestCase /** * Test editing an existing annotation. + * + * @dataProvider dataForEachAnnotations */ - public function testEditAnnotation() + public function testEditAnnotation($prefixUrl) { - /** @var Annotation $annotation */ - $annotation = $this->client->getContainer() - ->get('doctrine.orm.entity_manager') - ->getRepository('WallabagAnnotationBundle:Annotation') - ->findOneByUsername('admin'); + $em = $this->client->getContainer()->get('doctrine.orm.entity_manager'); + + $user = $em + ->getRepository('WallabagUserBundle:User') + ->findOneByUserName('admin'); + $entry = $em + ->getRepository('WallabagCoreBundle:Entry') + ->findOneByUsernameAndNotArchived('admin'); - $this->logInAs('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', ]); - $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); @@ -99,39 +145,55 @@ class AnnotationControllerTest extends WallabagAnnotationTestCase $this->assertEquals('my quote', $content['quote']); /** @var Annotation $annotationUpdated */ - $annotationUpdated = $this->client->getContainer() - ->get('doctrine.orm.entity_manager') + $annotationUpdated = $em ->getRepository('WallabagAnnotationBundle:Annotation') ->findOneById($annotation->getId()); $this->assertEquals('a modified annotation', $annotationUpdated->getText()); + + $em->remove($annotationUpdated); + $em->flush(); } /** * Test deleting an annotation. + * + * @dataProvider dataForEachAnnotations */ - public function testDeleteAnnotation() + public function testDeleteAnnotation($prefixUrl) { - /** @var Annotation $annotation */ - $annotation = $this->client->getContainer() - ->get('doctrine.orm.entity_manager') - ->getRepository('WallabagAnnotationBundle:Annotation') - ->findOneByUsername('admin'); + $em = $this->client->getContainer()->get('doctrine.orm.entity_manager'); + + $user = $em + ->getRepository('WallabagUserBundle:User') + ->findOneByUserName('admin'); + $entry = $em + ->getRepository('WallabagCoreBundle:Entry') + ->findOneByUsernameAndNotArchived('admin'); - $this->logInAs('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', ]); - $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()); -- cgit v1.2.3 From af2ff7c6da1bc8eb57753e7a5414377a0f6b618f Mon Sep 17 00:00:00 2001 From: Thomas Citharel Date: Sat, 15 Oct 2016 19:39:56 +0200 Subject: use new tcpdf library version --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 4f7ad291..ebc0a7dc 100644 --- a/composer.json +++ b/composer.json @@ -58,7 +58,7 @@ "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", -- cgit v1.2.3 From 206bade58a279d7f2e34c2dbada10366b90d2d6b Mon Sep 17 00:00:00 2001 From: Jeremy Benoist Date: Sat, 1 Oct 2016 09:26:32 +0200 Subject: Add ability to reset some datas - annotations - tags - entries --- app/DoctrineMigrations/Version20161001072726.php | 54 ++++++++ .../AnnotationBundle/Entity/Annotation.php | 2 +- .../CoreBundle/Controller/ConfigController.php | 42 ++++++ src/Wallabag/CoreBundle/Entity/Entry.php | 4 +- .../CoreBundle/Repository/TagRepository.php | 17 +++ .../Resources/translations/messages.da.yml | 14 +- .../Resources/translations/messages.de.yml | 14 +- .../Resources/translations/messages.en.yml | 14 +- .../Resources/translations/messages.es.yml | 14 +- .../Resources/translations/messages.fa.yml | 14 +- .../Resources/translations/messages.fr.yml | 12 +- .../Resources/translations/messages.it.yml | 14 +- .../Resources/translations/messages.oc.yml | 14 +- .../Resources/translations/messages.pl.yml | 10 ++ .../Resources/translations/messages.ro.yml | 14 +- .../Resources/translations/messages.tr.yml | 14 +- .../views/themes/material/Config/index.html.twig | 16 +++ .../CoreBundle/Controller/ConfigControllerTest.php | 145 +++++++++++++++++++++ 18 files changed, 406 insertions(+), 22 deletions(-) create mode 100644 app/DoctrineMigrations/Version20161001072726.php diff --git a/app/DoctrineMigrations/Version20161001072726.php b/app/DoctrineMigrations/Version20161001072726.php new file mode 100644 index 00000000..2e112949 --- /dev/null +++ b/app/DoctrineMigrations/Version20161001072726.php @@ -0,0 +1,54 @@ +container = $container; + } + + private function getTable($tableName) + { + return $this->container->getParameter('database_table_prefix') . $tableName; + } + + /** + * @param Schema $schema + */ + public function up(Schema $schema) + { + $this->addSql('ALTER TABLE '.$this->getTable('entry_tag').' DROP FOREIGN KEY FK_F035C9E5BA364942'); + $this->addSql('ALTER TABLE '.$this->getTable('entry_tag').' DROP FOREIGN KEY FK_F035C9E5BAD26311'); + $this->addSql('ALTER TABLE '.$this->getTable('entry_tag').' ADD CONSTRAINT FK_F035C9E5BA364942 FOREIGN KEY (entry_id) REFERENCES `entry` (id) ON DELETE CASCADE'); + $this->addSql('ALTER TABLE '.$this->getTable('entry_tag').' ADD CONSTRAINT FK_F035C9E5BAD26311 FOREIGN KEY (tag_id) REFERENCES `tag` (id) ON DELETE CASCADE'); + $this->addSql('ALTER TABLE '.$this->getTable('annotation').' DROP FOREIGN KEY FK_2E443EF2BA364942'); + $this->addSql('ALTER TABLE '.$this->getTable('annotation').' ADD CONSTRAINT FK_2E443EF2BA364942 FOREIGN KEY (entry_id) REFERENCES `entry` (id) ON DELETE CASCADE'); + } + + /** + * @param Schema $schema + */ + public function down(Schema $schema) + { + $this->abortIf($this->connection->getDatabasePlatform()->getName() == 'sqlite', 'Migration can only be executed safely on \'mysql\' or \'postgresql\'.'); + + $this->addSql('ALTER TABLE '.$this->getTable('annotation').' DROP FOREIGN KEY FK_2E443EF2BA364942'); + $this->addSql('ALTER TABLE '.$this->getTable('annotation').' ADD CONSTRAINT FK_2E443EF2BA364942 FOREIGN KEY (entry_id) REFERENCES entry (id)'); + $this->addSql('ALTER TABLE '.$this->getTable('entry_tag').' DROP FOREIGN KEY FK_F035C9E5BA364942'); + $this->addSql('ALTER TABLE '.$this->getTable('entry_tag').' DROP FOREIGN KEY FK_F035C9E5BAD26311'); + $this->addSql('ALTER TABLE '.$this->getTable('entry_tag').' ADD CONSTRAINT FK_F035C9E5BA364942 FOREIGN KEY (entry_id) REFERENCES entry (id)'); + $this->addSql('ALTER TABLE '.$this->getTable('entry_tag').' ADD CONSTRAINT FK_F035C9E5BAD26311 FOREIGN KEY (tag_id) REFERENCES tag (id)'); + } +} 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/CoreBundle/Controller/ConfigController.php b/src/Wallabag/CoreBundle/Controller/ConfigController.php index abd35c02..ccbf550a 100644 --- a/src/Wallabag/CoreBundle/Controller/ConfigController.php +++ b/src/Wallabag/CoreBundle/Controller/ConfigController.php @@ -224,6 +224,48 @@ 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': + $em->createQuery('DELETE FROM Wallabag\AnnotationBundle\Entity\Annotation a WHERE a.user = '.$this->getUser()->getId()) + ->execute(); + break; + + case 'tags': + $tags = $this->getDoctrine()->getRepository('WallabagCoreBundle:Tag')->findAllTags($this->getUser()->getId()); + + if (empty($tags)) { + break; + } + + $this->getDoctrine() + ->getRepository('WallabagCoreBundle:Entry') + ->removeTags($this->getUser()->getId(), $tags); + break; + + case 'entries': + $em->createQuery('DELETE FROM Wallabag\CoreBundle\Entity\Entry e WHERE e.user = '.$this->getUser()->getId()) + ->execute(); + } + + $this->get('session')->getFlashBag()->add( + 'notice', + 'flashes.config.notice.'.$type.'_reset' + ); + + return $this->redirect($this->generateUrl('config').'#set3'); + } + /** * Validate that a rule can be edited/deleted by the current user. * diff --git a/src/Wallabag/CoreBundle/Entity/Entry.php b/src/Wallabag/CoreBundle/Entity/Entry.php index f2da3f4d..dd49acf0 100644 --- a/src/Wallabag/CoreBundle/Entity/Entry.php +++ b/src/Wallabag/CoreBundle/Entity/Entry.php @@ -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/TagRepository.php b/src/Wallabag/CoreBundle/Repository/TagRepository.php index e76878d4..69661b12 100644 --- a/src/Wallabag/CoreBundle/Repository/TagRepository.php +++ b/src/Wallabag/CoreBundle/Repository/TagRepository.php @@ -52,6 +52,23 @@ class TagRepository extends EntityRepository ->getArrayResult(); } + /** + * Find all tags. + * + * @param int $userId + * + * @return array + */ + public function findAllTags($userId) + { + return $this->createQueryBuilder('t') + ->select('t') + ->leftJoin('t.entries', 'e') + ->where('e.user = :userId')->setParameter('userId', $userId) + ->getQuery() + ->getResult(); + } + /** * Used only in test case to get a tag for our entry. * diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.da.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.da.yml index f5548a21..7c8ae66e 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.da.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.da.yml @@ -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%' diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.de.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.de.yml index 9edd7fb7..20f9753b 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.de.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.de.yml @@ -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' diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.en.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.en.yml index b86145a0..35dde535 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.en.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.en.yml @@ -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%' diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.es.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.es.yml index b7187f50..13f2e977 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.es.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.es.yml @@ -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%' diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml index 0751752b..5ee1f62d 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml @@ -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% ذخیره شده بود' diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml index 8d19ccb1..7a98f133 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml @@ -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 IRREVERSIBLE) 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 IRREVERSIBLES ! + annotations: Supprimer TOUTES les annotations + tags: Supprimer TOUS les tags + entries: Supprimer TOUS les articles + confirm: Êtes-vous vraiment vraiment sûr ? (C'EST IRREVERSIBLE) form_password: old_password_label: 'Mot de passe actuel' new_password_label: 'Nouveau mot de passe' @@ -462,6 +469,9 @@ 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%' diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.it.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.it.yml index 4d3452ea..bc4448bd 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.it.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.it.yml @@ -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%' diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml index f14213c6..7d1a801a 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml @@ -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%' diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml index 6f22f90d..b05a9dfd 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml @@ -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%' diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml index 29db9c3e..571452c0 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml @@ -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%' diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml index 41e8e576..8e429653 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml @@ -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%' 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 8434508d..79826e0f 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 @@ -168,6 +168,22 @@ {{ 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 %}


diff --git a/tests/Wallabag/CoreBundle/Controller/ConfigControllerTest.php b/tests/Wallabag/CoreBundle/Controller/ConfigControllerTest.php index 5faa0130..8d0644d1 100644 --- a/tests/Wallabag/CoreBundle/Controller/ConfigControllerTest.php +++ b/tests/Wallabag/CoreBundle/Controller/ConfigControllerTest.php @@ -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'); + } } -- cgit v1.2.3 From 98efffc2a62820bad347a0f93840c48fa57f8cc3 Mon Sep 17 00:00:00 2001 From: Jeremy Benoist Date: Sat, 1 Oct 2016 10:52:13 +0200 Subject: Fix emoji insertion in MySQL Switch to utf8mb4 instead of utf8 because f*** MySQL See https://github.com/doctrine/dbal/pull/851 --- app/config/config.yml | 2 +- app/config/config_test.yml | 2 +- app/config/parameters.yml.dist | 14 ++++++++------ app/config/parameters_test.yml | 1 + app/config/tests/parameters_test.mysql.yml | 1 + app/config/tests/parameters_test.pgsql.yml | 1 + app/config/tests/parameters_test.sqlite.yml | 1 + src/Wallabag/CoreBundle/Entity/Entry.php | 2 +- 8 files changed, 15 insertions(+), 9 deletions(-) diff --git a/app/config/config.yml b/app/config/config.yml index b4760073..9dbc9d7c 100644 --- a/app/config/config.yml +++ b/app/config/config.yml @@ -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 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..a33ea37b 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 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/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..0efbe786 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: utf8mb4 diff --git a/src/Wallabag/CoreBundle/Entity/Entry.php b/src/Wallabag/CoreBundle/Entity/Entry.php index dd49acf0..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())") */ -- cgit v1.2.3 From 191564b7f71d01fb4c597c9b9641e23db564278d Mon Sep 17 00:00:00 2001 From: Jeremy Benoist Date: Sat, 1 Oct 2016 14:01:13 +0200 Subject: Add custom doctrine subscriber for SQLite MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since SQLite doesn’t handle cascade remove by default, we need to handle it manually. Also some refacto --- .../Repository/AnnotationRepository.php | 13 ++++ .../CoreBundle/Controller/ConfigController.php | 41 +++++++++---- .../CoreBundle/Repository/EntryRepository.php | 13 ++++ .../CoreBundle/Resources/config/services.yml | 18 ++++++ .../Subscriber/SQLiteCascadeDeleteSubscriber.php | 70 ++++++++++++++++++++++ 5 files changed, 144 insertions(+), 11 deletions(-) create mode 100644 src/Wallabag/CoreBundle/Subscriber/SQLiteCascadeDeleteSubscriber.php diff --git a/src/Wallabag/AnnotationBundle/Repository/AnnotationRepository.php b/src/Wallabag/AnnotationBundle/Repository/AnnotationRepository.php index 5f7da70e..d999dc0f 100644 --- a/src/Wallabag/AnnotationBundle/Repository/AnnotationRepository.php +++ b/src/Wallabag/AnnotationBundle/Repository/AnnotationRepository.php @@ -106,4 +106,17 @@ 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) + ->execute(); + } } diff --git a/src/Wallabag/CoreBundle/Controller/ConfigController.php b/src/Wallabag/CoreBundle/Controller/ConfigController.php index ccbf550a..faa85d16 100644 --- a/src/Wallabag/CoreBundle/Controller/ConfigController.php +++ b/src/Wallabag/CoreBundle/Controller/ConfigController.php @@ -237,25 +237,26 @@ class ConfigController extends Controller switch ($type) { case 'annotations': - $em->createQuery('DELETE FROM Wallabag\AnnotationBundle\Entity\Annotation a WHERE a.user = '.$this->getUser()->getId()) - ->execute(); + $this->getDoctrine() + ->getRepository('WallabagAnnotationBundle:Annotation') + ->removeAllByUserId($this->getUser()->getId()); break; case 'tags': - $tags = $this->getDoctrine()->getRepository('WallabagCoreBundle:Tag')->findAllTags($this->getUser()->getId()); + $this->removeAllTagsByUserId($this->getUser()->getId()); + break; - if (empty($tags)) { - 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()); + $this->removeAllTagsByUserId($this->getUser()->getId()); } $this->getDoctrine() ->getRepository('WallabagCoreBundle:Entry') - ->removeTags($this->getUser()->getId(), $tags); - break; - - case 'entries': - $em->createQuery('DELETE FROM Wallabag\CoreBundle\Entity\Entry e WHERE e.user = '.$this->getUser()->getId()) - ->execute(); + ->removeAllByUserId($this->getUser()->getId()); } $this->get('session')->getFlashBag()->add( @@ -266,6 +267,24 @@ class ConfigController extends Controller return $this->redirect($this->generateUrl('config').'#set3'); } + /** + * Remove all tags for a given user. + * + * @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); + } + /** * Validate that a rule can be edited/deleted by the current user. * diff --git a/src/Wallabag/CoreBundle/Repository/EntryRepository.php b/src/Wallabag/CoreBundle/Repository/EntryRepository.php index cd2b47b9..8704a2a6 100644 --- a/src/Wallabag/CoreBundle/Repository/EntryRepository.php +++ b/src/Wallabag/CoreBundle/Repository/EntryRepository.php @@ -329,4 +329,17 @@ 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) + ->execute(); + } } diff --git a/src/Wallabag/CoreBundle/Resources/config/services.yml b/src/Wallabag/CoreBundle/Resources/config/services.yml index a4b727f4..540d21f1 100644 --- a/src/Wallabag/CoreBundle/Resources/config/services.yml +++ b/src/Wallabag/CoreBundle/Resources/config/services.yml @@ -88,6 +88,17 @@ services: arguments: - WallabagCoreBundle:Tag + wallabag_core.listener.registration_confirmed: + class: Wallabag\CoreBundle\EventListener\RegistrationConfirmedListener + arguments: + - "@doctrine.orm.entity_manager" + - "%wallabag_core.theme%" + - "%wallabag_core.items_on_page%" + - "%wallabag_core.rss_limit%" + - "%wallabag_core.language%" + tags: + - { name: kernel.event_subscriber } + wallabag_core.helper.entries_export: class: Wallabag\CoreBundle\Helper\EntriesExport arguments: @@ -129,3 +140,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/Subscriber/SQLiteCascadeDeleteSubscriber.php b/src/Wallabag/CoreBundle/Subscriber/SQLiteCascadeDeleteSubscriber.php new file mode 100644 index 00000000..d5180577 --- /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(); + } +} -- cgit v1.2.3 From 2f82e7f8e1827423e2dbe4a91089c66d3afff367 Mon Sep 17 00:00:00 2001 From: Jeremy Benoist Date: Sat, 8 Oct 2016 10:57:57 +0200 Subject: Cleanup subscriber / listener definition --- app/config/parameters_test.yml | 2 +- src/Wallabag/CoreBundle/Resources/config/services.yml | 11 ----------- src/Wallabag/UserBundle/Resources/config/services.yml | 2 +- 3 files changed, 2 insertions(+), 13 deletions(-) diff --git a/app/config/parameters_test.yml b/app/config/parameters_test.yml index 5f2e25bb..3111da47 100644 --- a/app/config/parameters_test.yml +++ b/app/config/parameters_test.yml @@ -6,4 +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 + test_database_charset: utf8mb4 diff --git a/src/Wallabag/CoreBundle/Resources/config/services.yml b/src/Wallabag/CoreBundle/Resources/config/services.yml index 540d21f1..048a72fc 100644 --- a/src/Wallabag/CoreBundle/Resources/config/services.yml +++ b/src/Wallabag/CoreBundle/Resources/config/services.yml @@ -88,17 +88,6 @@ services: arguments: - WallabagCoreBundle:Tag - wallabag_core.listener.registration_confirmed: - class: Wallabag\CoreBundle\EventListener\RegistrationConfirmedListener - arguments: - - "@doctrine.orm.entity_manager" - - "%wallabag_core.theme%" - - "%wallabag_core.items_on_page%" - - "%wallabag_core.rss_limit%" - - "%wallabag_core.language%" - tags: - - { name: kernel.event_subscriber } - wallabag_core.helper.entries_export: class: Wallabag\CoreBundle\Helper\EntriesExport arguments: 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" -- cgit v1.2.3 From f71e55ac886a26813ca1171c0aca4921aa8f00ad Mon Sep 17 00:00:00 2001 From: Jeremy Benoist Date: Sat, 8 Oct 2016 11:05:03 +0200 Subject: Avoid orphan tags --- src/Wallabag/CoreBundle/Controller/ConfigController.php | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/Wallabag/CoreBundle/Controller/ConfigController.php b/src/Wallabag/CoreBundle/Controller/ConfigController.php index faa85d16..e2484064 100644 --- a/src/Wallabag/CoreBundle/Controller/ConfigController.php +++ b/src/Wallabag/CoreBundle/Controller/ConfigController.php @@ -251,9 +251,11 @@ class ConfigController extends Controller // 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()); - $this->removeAllTagsByUserId($this->getUser()->getId()); } + // manually remove tags first to avoid orphan tag + $this->removeAllTagsByUserId($this->getUser()->getId()); + $this->getDoctrine() ->getRepository('WallabagCoreBundle:Entry') ->removeAllByUserId($this->getUser()->getId()); @@ -268,7 +270,7 @@ class ConfigController extends Controller } /** - * Remove all tags for a given user. + * Remove all tags for a given user and cleanup orphan tags * * @param int $userId */ @@ -283,6 +285,16 @@ class ConfigController extends Controller $this->getDoctrine() ->getRepository('WallabagCoreBundle:Entry') ->removeTags($userId, $tags); + + $em = $this->getDoctrine()->getManager(); + + foreach ($tags as $tag) { + if (count($tag->getEntries()) === 0) { + $em->remove($tag); + } + } + + $em->flush(); } /** -- cgit v1.2.3 From ca8b49f46e9e45875ef0f9dc88a8315489152e82 Mon Sep 17 00:00:00 2001 From: Jeremy Benoist Date: Sat, 8 Oct 2016 11:13:55 +0200 Subject: Add baggy reset part --- .../views/themes/baggy/Config/index.html.twig | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) 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 54508b6d..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,6 +146,28 @@ {% endif %} +

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

+
+

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

+ +
+ {{ form_widget(form.user._token) }} {{ form_widget(form.user.save) }} -- cgit v1.2.3 From 8c61fd12b1df50d481e9f82c39521cca7b8ad060 Mon Sep 17 00:00:00 2001 From: Jeremy Benoist Date: Sat, 8 Oct 2016 11:14:09 +0200 Subject: CS --- src/Wallabag/AnnotationBundle/Repository/AnnotationRepository.php | 4 ++-- src/Wallabag/CoreBundle/Controller/ConfigController.php | 7 ++++--- src/Wallabag/CoreBundle/Repository/EntryRepository.php | 4 ++-- .../CoreBundle/Subscriber/SQLiteCascadeDeleteSubscriber.php | 4 ++-- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/Wallabag/AnnotationBundle/Repository/AnnotationRepository.php b/src/Wallabag/AnnotationBundle/Repository/AnnotationRepository.php index d999dc0f..c81a2614 100644 --- a/src/Wallabag/AnnotationBundle/Repository/AnnotationRepository.php +++ b/src/Wallabag/AnnotationBundle/Repository/AnnotationRepository.php @@ -109,9 +109,9 @@ class AnnotationRepository extends EntityRepository /** * Remove all annotations for a user id. - * Used when a user want to reset all informations + * Used when a user want to reset all informations. * - * @param int $userId + * @param int $userId */ public function removeAllByUserId($userId) { diff --git a/src/Wallabag/CoreBundle/Controller/ConfigController.php b/src/Wallabag/CoreBundle/Controller/ConfigController.php index e2484064..8d391917 100644 --- a/src/Wallabag/CoreBundle/Controller/ConfigController.php +++ b/src/Wallabag/CoreBundle/Controller/ConfigController.php @@ -253,7 +253,7 @@ class ConfigController extends Controller $this->getDoctrine()->getRepository('WallabagAnnotationBundle:Annotation')->removeAllByUserId($this->getUser()->getId()); } - // manually remove tags first to avoid orphan tag + // manually remove tags to avoid orphan tag $this->removeAllTagsByUserId($this->getUser()->getId()); $this->getDoctrine() @@ -270,9 +270,9 @@ class ConfigController extends Controller } /** - * Remove all tags for a given user and cleanup orphan tags + * Remove all tags for a given user and cleanup orphan tags. * - * @param int $userId + * @param int $userId */ private function removeAllTagsByUserId($userId) { @@ -286,6 +286,7 @@ class ConfigController extends Controller ->getRepository('WallabagCoreBundle:Entry') ->removeTags($userId, $tags); + // cleanup orphan tags $em = $this->getDoctrine()->getManager(); foreach ($tags as $tag) { diff --git a/src/Wallabag/CoreBundle/Repository/EntryRepository.php b/src/Wallabag/CoreBundle/Repository/EntryRepository.php index 8704a2a6..5df5eff5 100644 --- a/src/Wallabag/CoreBundle/Repository/EntryRepository.php +++ b/src/Wallabag/CoreBundle/Repository/EntryRepository.php @@ -332,9 +332,9 @@ class EntryRepository extends EntityRepository /** * Remove all entries for a user id. - * Used when a user want to reset all informations + * Used when a user want to reset all informations. * - * @param int $userId + * @param int $userId */ public function removeAllByUserId($userId) { diff --git a/src/Wallabag/CoreBundle/Subscriber/SQLiteCascadeDeleteSubscriber.php b/src/Wallabag/CoreBundle/Subscriber/SQLiteCascadeDeleteSubscriber.php index d5180577..f7210bd3 100644 --- a/src/Wallabag/CoreBundle/Subscriber/SQLiteCascadeDeleteSubscriber.php +++ b/src/Wallabag/CoreBundle/Subscriber/SQLiteCascadeDeleteSubscriber.php @@ -1,8 +1,8 @@ Date: Tue, 11 Oct 2016 21:45:43 +0200 Subject: Use statements & update translation --- app/config/parameters.yml.dist | 2 +- docs/en/user/parameters.rst | 1 + .../Repository/AnnotationRepository.php | 3 +- .../CoreBundle/Controller/TagController.php | 10 +++---- .../CoreBundle/Repository/EntryRepository.php | 3 +- .../CoreBundle/Repository/TagRepository.php | 33 ++++++++-------------- .../Resources/translations/messages.fr.yml | 10 +++---- 7 files changed, 28 insertions(+), 34 deletions(-) diff --git a/app/config/parameters.yml.dist b/app/config/parameters.yml.dist index a33ea37b..436eeeda 100644 --- a/app/config/parameters.yml.dist +++ b/app/config/parameters.yml.dist @@ -20,7 +20,7 @@ parameters: database_table_prefix: wallabag_ database_socket: null # with MySQL, use "utf8mb4" if got problem with content with emojis - database_charset: utf8 + database_charset: utf8mb4 mailer_transport: smtp mailer_host: 127.0.0.1 diff --git a/docs/en/user/parameters.rst b/docs/en/user/parameters.rst index 79c50871..c4345b74 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 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 you should use utf8, for other use utf8mb4 which handle emoji" .. csv-table:: Configuration to send emails from wallabag :header: "name", "default", "description" diff --git a/src/Wallabag/AnnotationBundle/Repository/AnnotationRepository.php b/src/Wallabag/AnnotationBundle/Repository/AnnotationRepository.php index c81a2614..52989bcf 100644 --- a/src/Wallabag/AnnotationBundle/Repository/AnnotationRepository.php +++ b/src/Wallabag/AnnotationBundle/Repository/AnnotationRepository.php @@ -116,7 +116,8 @@ class AnnotationRepository extends EntityRepository public function removeAllByUserId($userId) { $this->getEntityManager() - ->createQuery('DELETE FROM Wallabag\AnnotationBundle\Entity\Annotation a WHERE a.user = '.$userId) + ->createQuery('DELETE FROM Wallabag\AnnotationBundle\Entity\Annotation a WHERE a.user = :userId') + ->setParameter('userId', $userId) ->execute(); } } 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/Repository/EntryRepository.php b/src/Wallabag/CoreBundle/Repository/EntryRepository.php index 5df5eff5..14616d88 100644 --- a/src/Wallabag/CoreBundle/Repository/EntryRepository.php +++ b/src/Wallabag/CoreBundle/Repository/EntryRepository.php @@ -339,7 +339,8 @@ class EntryRepository extends EntityRepository public function removeAllByUserId($userId) { $this->getEntityManager() - ->createQuery('DELETE FROM Wallabag\CoreBundle\Entity\Entry e WHERE e.user = '.$userId) + ->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 69661b12..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,32 +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(); - } - /** - * Find all tags. - * - * @param int $userId - * - * @return array - */ - public function findAllTags($userId) - { - return $this->createQueryBuilder('t') - ->select('t') - ->leftJoin('t.entries', 'e') - ->where('e.user = :userId')->setParameter('userId', $userId) - ->getQuery() - ->getResult(); + $tags = []; + foreach ($ids as $id) { + $tags[] = $this->find($id); + } + + return $tags; } /** diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml index 7a98f133..14bdbbc7 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml @@ -91,15 +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 IRREVERSIBLE) + 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 IRREVERSIBLES ! + 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 IRREVERSIBLE) + 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' @@ -398,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: @@ -474,7 +474,7 @@ flashes: 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' -- cgit v1.2.3 From fc79f1ffa82529e5340d115b2f438ac5952e9cb0 Mon Sep 17 00:00:00 2001 From: Jeremy Benoist Date: Sat, 22 Oct 2016 13:41:03 +0200 Subject: Add verification check for MySQL version Must now be >= 5.5.4 --- app/config/parameters.yml.dist | 4 ++-- app/config/tests/parameters_test.sqlite.yml | 2 +- src/Wallabag/CoreBundle/Command/InstallCommand.php | 18 +++++++++++++++++- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/app/config/parameters.yml.dist b/app/config/parameters.yml.dist index 436eeeda..7a22cb98 100644 --- a/app/config/parameters.yml.dist +++ b/app/config/parameters.yml.dist @@ -19,8 +19,8 @@ parameters: database_path: "%kernel.root_dir%/../data/db/wallabag.sqlite" database_table_prefix: wallabag_ database_socket: null - # with MySQL, use "utf8mb4" if got problem with content with emojis - database_charset: utf8mb4 + # with MySQL, use "utf8mb4" if you got problem with content with emojis + database_charset: utf8 mailer_transport: smtp mailer_host: 127.0.0.1 diff --git a/app/config/tests/parameters_test.sqlite.yml b/app/config/tests/parameters_test.sqlite.yml index 0efbe786..b8a5f41a 100644 --- a/app/config/tests/parameters_test.sqlite.yml +++ b/app/config/tests/parameters_test.sqlite.yml @@ -6,4 +6,4 @@ parameters: test_database_user: ~ test_database_password: ~ test_database_path: "%kernel.root_dir%/../data/db/wallabag_test.sqlite" - test_database_charset: utf8mb4 + test_database_charset: utf8 diff --git a/src/Wallabag/CoreBundle/Command/InstallCommand.php b/src/Wallabag/CoreBundle/Command/InstallCommand.php index 59110782..6f9aff60 100644 --- a/src/Wallabag/CoreBundle/Command/InstallCommand.php +++ b/src/Wallabag/CoreBundle/Command/InstallCommand.php @@ -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!'; -- cgit v1.2.3 From 5751b41491b9fd69c5b553c95e70a90fbf975aeb Mon Sep 17 00:00:00 2001 From: Jeremy Benoist Date: Sat, 22 Oct 2016 14:01:58 +0200 Subject: Add migration for MySQL utf8mb4 --- app/DoctrineMigrations/Version20161022134138.php | 77 ++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 app/DoctrineMigrations/Version20161022134138.php 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;'); + + } +} -- cgit v1.2.3 From 88d5d94dcbd658fa3d45011e19119f31cd03d2c8 Mon Sep 17 00:00:00 2001 From: Jeremy Benoist Date: Sat, 22 Oct 2016 14:05:59 +0200 Subject: Lowercase wallabag --- src/Wallabag/CoreBundle/Command/InstallCommand.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Wallabag/CoreBundle/Command/InstallCommand.php b/src/Wallabag/CoreBundle/Command/InstallCommand.php index 6f9aff60..511c34bd 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'); } @@ -147,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(''); -- cgit v1.2.3 From 5ce1528953998ee2957cd548ee123870b82f4079 Mon Sep 17 00:00:00 2001 From: Jeremy Benoist Date: Sun, 23 Oct 2016 12:35:57 +0200 Subject: Fix migrations --- app/DoctrineMigrations/Version20160410190541.php | 12 ++++--- app/DoctrineMigrations/Version20160812120952.php | 15 ++++++--- app/DoctrineMigrations/Version20160911214952.php | 4 +-- app/DoctrineMigrations/Version20160916201049.php | 6 ++-- app/DoctrineMigrations/Version20161001072726.php | 37 ++++++++++++++-------- src/Wallabag/CoreBundle/Command/InstallCommand.php | 2 +- 6 files changed, 48 insertions(+), 28 deletions(-) diff --git a/app/DoctrineMigrations/Version20160410190541.php b/app/DoctrineMigrations/Version20160410190541.php index 4014857b..c41b0465 100644 --- a/app/DoctrineMigrations/Version20160410190541.php +++ b/app/DoctrineMigrations/Version20160410190541.php @@ -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'"); } } diff --git a/app/DoctrineMigrations/Version20160812120952.php b/app/DoctrineMigrations/Version20160812120952.php index a8d3bcf2..39423e2f 100644 --- a/app/DoctrineMigrations/Version20160812120952.php +++ b/app/DoctrineMigrations/Version20160812120952.php @@ -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'); } } diff --git a/app/DoctrineMigrations/Version20160911214952.php b/app/DoctrineMigrations/Version20160911214952.php index 35809cec..3f988ccf 100644 --- a/app/DoctrineMigrations/Version20160911214952.php +++ b/app/DoctrineMigrations/Version20160911214952.php @@ -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\')'); } /** diff --git a/app/DoctrineMigrations/Version20160916201049.php b/app/DoctrineMigrations/Version20160916201049.php index 202901e6..fc4e700a 100644 --- a/app/DoctrineMigrations/Version20160916201049.php +++ b/app/DoctrineMigrations/Version20160916201049.php @@ -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 index 2e112949..237db932 100644 --- a/app/DoctrineMigrations/Version20161001072726.php +++ b/app/DoctrineMigrations/Version20161001072726.php @@ -29,12 +29,28 @@ class Version20161001072726 extends AbstractMigration implements ContainerAwareI */ public function up(Schema $schema) { - $this->addSql('ALTER TABLE '.$this->getTable('entry_tag').' DROP FOREIGN KEY FK_F035C9E5BA364942'); - $this->addSql('ALTER TABLE '.$this->getTable('entry_tag').' DROP FOREIGN KEY FK_F035C9E5BAD26311'); - $this->addSql('ALTER TABLE '.$this->getTable('entry_tag').' ADD CONSTRAINT FK_F035C9E5BA364942 FOREIGN KEY (entry_id) REFERENCES `entry` (id) ON DELETE CASCADE'); - $this->addSql('ALTER TABLE '.$this->getTable('entry_tag').' ADD CONSTRAINT FK_F035C9E5BAD26311 FOREIGN KEY (tag_id) REFERENCES `tag` (id) ON DELETE CASCADE'); - $this->addSql('ALTER TABLE '.$this->getTable('annotation').' DROP FOREIGN KEY FK_2E443EF2BA364942'); - $this->addSql('ALTER TABLE '.$this->getTable('annotation').' ADD CONSTRAINT FK_2E443EF2BA364942 FOREIGN KEY (entry_id) REFERENCES `entry` (id) ON DELETE CASCADE'); + $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'); } /** @@ -42,13 +58,6 @@ class Version20161001072726 extends AbstractMigration implements ContainerAwareI */ public function down(Schema $schema) { - $this->abortIf($this->connection->getDatabasePlatform()->getName() == 'sqlite', 'Migration can only be executed safely on \'mysql\' or \'postgresql\'.'); - - $this->addSql('ALTER TABLE '.$this->getTable('annotation').' DROP FOREIGN KEY FK_2E443EF2BA364942'); - $this->addSql('ALTER TABLE '.$this->getTable('annotation').' ADD CONSTRAINT FK_2E443EF2BA364942 FOREIGN KEY (entry_id) REFERENCES entry (id)'); - $this->addSql('ALTER TABLE '.$this->getTable('entry_tag').' DROP FOREIGN KEY FK_F035C9E5BA364942'); - $this->addSql('ALTER TABLE '.$this->getTable('entry_tag').' DROP FOREIGN KEY FK_F035C9E5BAD26311'); - $this->addSql('ALTER TABLE '.$this->getTable('entry_tag').' ADD CONSTRAINT FK_F035C9E5BA364942 FOREIGN KEY (entry_id) REFERENCES entry (id)'); - $this->addSql('ALTER TABLE '.$this->getTable('entry_tag').' ADD CONSTRAINT FK_F035C9E5BAD26311 FOREIGN KEY (tag_id) REFERENCES tag (id)'); + throw new SkipMigrationException('Too complex ...'); } } diff --git a/src/Wallabag/CoreBundle/Command/InstallCommand.php b/src/Wallabag/CoreBundle/Command/InstallCommand.php index 511c34bd..82cd9daf 100644 --- a/src/Wallabag/CoreBundle/Command/InstallCommand.php +++ b/src/Wallabag/CoreBundle/Command/InstallCommand.php @@ -118,7 +118,7 @@ class InstallCommand extends ContainerAwareCommand $rows[] = [ 'Database version', 'ERROR!', - 'Your MySQL version ('.$version.') is too old, consider upgrading ('.$minimalVersion.'+).' + 'Your MySQL version ('.$version.') is too old, consider upgrading ('.$minimalVersion.'+).', ]; } } -- cgit v1.2.3 From f623516e107c8146f87d815f033cecb5d812466a Mon Sep 17 00:00:00 2001 From: Jeremy Benoist Date: Mon, 24 Oct 2016 10:10:38 +0200 Subject: SQLite should use utf8, not utf8mb4 --- app/config/parameters_test.yml | 2 +- docs/en/user/parameters.rst | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/config/parameters_test.yml b/app/config/parameters_test.yml index 3111da47..5f2e25bb 100644 --- a/app/config/parameters_test.yml +++ b/app/config/parameters_test.yml @@ -6,4 +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: utf8mb4 + test_database_charset: utf8 diff --git a/docs/en/user/parameters.rst b/docs/en/user/parameters.rst index c4345b74..2fca020e 100644 --- a/docs/en/user/parameters.rst +++ b/docs/en/user/parameters.rst @@ -11,8 +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_charset", "utf8mb4", "For PostgreSQL you should use utf8, for other use utf8mb4 which handle emoji" + "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" -- cgit v1.2.3 From cd99bfae689f1bb82f1ab9c2a09eaf2dd77b823e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Rumi=C5=84ski?= Date: Mon, 24 Oct 2016 19:49:33 +0200 Subject: Update messages.pl.yml translate reset section to polish --- .../Resources/translations/messages.pl.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml index 8eef998b..a2989dbd 100644 --- a/src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml +++ b/src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml @@ -94,12 +94,12 @@ config: 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) + title: Reset (niebezpieczna strefa) + description: Poniższe przyciski pozwalają usunąć pewne informacje z twojego konta. Uważaj te operacje są NIEODWRACALNE. + annotations: Usuń WSZYSTKIE adnotacje + tags: Usuń WSZYSTKIE tagi + entries: usuń WSZYTSTKIE wpisy + confirm: Jesteś pewien? (tej operacji NIE MOŻNA cofnąć) form_password: old_password_label: 'Stare hasło' new_password_label: 'Nowe hasło' @@ -458,7 +458,7 @@ user: back_to_list: Powrót do listy error: - # page_title: An error occurred + page_title: Wystąpił błąd flashes: config: @@ -472,9 +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 + annotations_reset: Zresetuj adnotacje + tags_reset: Zresetuj tagi + entries_reset: Zresetuj wpisy entry: notice: entry_already_saved: 'Wpis już został dodany %date%' -- cgit v1.2.3 From 23406ca3f12303759ecb46974d6bcb22fb0e037b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolas=20L=C5=93uillet?= Date: Mon, 24 Oct 2016 21:56:28 +0200 Subject: Added relation between API Client and User Fix #2062 --- app/DoctrineMigrations/Version20161024212538.php | 47 ++++++++++++++++++++++ .../ApiBundle/Controller/DeveloperController.php | 8 +++- src/Wallabag/ApiBundle/Entity/Client.php | 17 +++++++- src/Wallabag/UserBundle/Entity/User.php | 26 ++++++++++++ 4 files changed, 95 insertions(+), 3 deletions(-) create mode 100644 app/DoctrineMigrations/Version20161024212538.php diff --git a/app/DoctrineMigrations/Version20161024212538.php b/app/DoctrineMigrations/Version20161024212538.php new file mode 100644 index 00000000..75973b33 --- /dev/null +++ b/app/DoctrineMigrations/Version20161024212538.php @@ -0,0 +1,47 @@ +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\'.'); + + $this->addSql('ALTER TABLE '.$this->getTable('oauth2_clients').' ADD CONSTRAINT FK_clients_user_clients FOREIGN KEY (user_id) REFERENCES '.$this->getTable('user').' (id) ON DELETE CASCADE'); + } + + /** + * @param Schema $schema + */ + public function down(Schema $schema) + { + + } +} diff --git a/src/Wallabag/ApiBundle/Controller/DeveloperController.php b/src/Wallabag/ApiBundle/Controller/DeveloperController.php index 5a36a260..550c0608 100644 --- a/src/Wallabag/ApiBundle/Controller/DeveloperController.php +++ b/src/Wallabag/ApiBundle/Controller/DeveloperController.php @@ -19,7 +19,7 @@ class DeveloperController extends Controller */ public function indexAction() { - $clients = $this->getDoctrine()->getRepository('WallabagApiBundle:Client')->findAll(); + $clients = $this->getDoctrine()->getRepository('WallabagApiBundle:Client')->findByUser($this->getUser()->getId()); return $this->render('@WallabagCore/themes/common/Developer/index.html.twig', [ 'clients' => $clients, @@ -38,7 +38,7 @@ class DeveloperController extends Controller public function createClientAction(Request $request) { $em = $this->getDoctrine()->getManager(); - $client = new Client(); + $client = new Client($this->getUser()); $clientForm = $this->createForm(ClientType::class, $client); $clientForm->handleRequest($request); @@ -75,6 +75,10 @@ class DeveloperController extends Controller */ public function deleteClientAction(Client $client) { + if (null === $this->getUser() || $client->getUser()->getId() != $this->getUser()->getId()) { + throw $this->createAccessDeniedException('You can not access this client.'); + } + $em = $this->getDoctrine()->getManager(); $em->remove($client); $em->flush(); diff --git a/src/Wallabag/ApiBundle/Entity/Client.php b/src/Wallabag/ApiBundle/Entity/Client.php index f7898ac8..427a4c7f 100644 --- a/src/Wallabag/ApiBundle/Entity/Client.php +++ b/src/Wallabag/ApiBundle/Entity/Client.php @@ -4,6 +4,7 @@ namespace Wallabag\ApiBundle\Entity; use Doctrine\ORM\Mapping as ORM; use FOS\OAuthServerBundle\Entity\Client as BaseClient; +use Wallabag\UserBundle\Entity\User; /** * @ORM\Table("oauth2_clients") @@ -35,9 +36,15 @@ class Client extends BaseClient */ protected $accessTokens; - public function __construct() + /** + * @ORM\ManyToOne(targetEntity="Wallabag\UserBundle\Entity\User", inversedBy="clients") + */ + private $user; + + public function __construct(User $user) { parent::__construct(); + $this->user = $user; } /** @@ -63,4 +70,12 @@ class Client extends BaseClient return $this; } + + /** + * @return User + */ + public function getUser() + { + return $this->user; + } } diff --git a/src/Wallabag/UserBundle/Entity/User.php b/src/Wallabag/UserBundle/Entity/User.php index d98ae76a..3a167de7 100644 --- a/src/Wallabag/UserBundle/Entity/User.php +++ b/src/Wallabag/UserBundle/Entity/User.php @@ -11,6 +11,7 @@ use JMS\Serializer\Annotation\ExclusionPolicy; use JMS\Serializer\Annotation\Expose; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Component\Security\Core\User\UserInterface; +use Wallabag\ApiBundle\Entity\Client; use Wallabag\CoreBundle\Entity\Config; use Wallabag\CoreBundle\Entity\Entry; @@ -84,6 +85,11 @@ class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterf */ private $trusted; + /** + * @ORM\OneToMany(targetEntity="Wallabag\ApiBundle\Entity\Client", mappedBy="user", cascade={"remove"}) + */ + protected $clients; + public function __construct() { parent::__construct(); @@ -240,4 +246,24 @@ class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterf return false; } + + /** + * @param Client $client + * + * @return User + */ + public function addClient(Client $client) + { + $this->clients[] = $client; + + return $this; + } + + /** + * @return ArrayCollection + */ + public function getClients() + { + return $this->clients; + } } -- cgit v1.2.3 From f24ea59ea4e854d8a699f51a7347af9d4a222de8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolas=20L=C5=93uillet?= Date: Fri, 28 Oct 2016 10:55:39 +0200 Subject: Fixed migration and added tests --- app/DoctrineMigrations/Version20161024212538.php | 1 + .../Controller/AnnotationControllerTest.php | 2 +- .../ApiBundle/Controller/DeveloperControllerTest.php | 19 ++++++++++++++++--- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/app/DoctrineMigrations/Version20161024212538.php b/app/DoctrineMigrations/Version20161024212538.php index 75973b33..b9dc500c 100644 --- a/app/DoctrineMigrations/Version20161024212538.php +++ b/app/DoctrineMigrations/Version20161024212538.php @@ -34,6 +34,7 @@ class Version20161024212538 extends AbstractMigration implements ContainerAwareI { $this->skipIf($this->connection->getDatabasePlatform()->getName() == 'sqlite', 'Migration can only be executed safely on \'mysql\' or \'postgresql\'.'); + $this->addSql('ALTER TABLE '.$this->getTable('oauth2_clients').' ADD `user_id` INT(11) DEFAULT NULL'); $this->addSql('ALTER TABLE '.$this->getTable('oauth2_clients').' ADD CONSTRAINT FK_clients_user_clients FOREIGN KEY (user_id) REFERENCES '.$this->getTable('user').' (id) ON DELETE CASCADE'); } diff --git a/tests/Wallabag/AnnotationBundle/Controller/AnnotationControllerTest.php b/tests/Wallabag/AnnotationBundle/Controller/AnnotationControllerTest.php index cee0b847..81f9e9ec 100644 --- a/tests/Wallabag/AnnotationBundle/Controller/AnnotationControllerTest.php +++ b/tests/Wallabag/AnnotationBundle/Controller/AnnotationControllerTest.php @@ -11,7 +11,7 @@ class AnnotationControllerTest extends WallabagAnnotationTestCase /** * 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) + * - and User POV (when user use the web interface - using javascript - to manage annotations). */ public function dataForEachAnnotations() { diff --git a/tests/Wallabag/ApiBundle/Controller/DeveloperControllerTest.php b/tests/Wallabag/ApiBundle/Controller/DeveloperControllerTest.php index 95befa9c..6659443b 100644 --- a/tests/Wallabag/ApiBundle/Controller/DeveloperControllerTest.php +++ b/tests/Wallabag/ApiBundle/Controller/DeveloperControllerTest.php @@ -82,11 +82,24 @@ class DeveloperControllerTest extends WallabagCoreTestCase public function testRemoveClient() { - $this->logInAs('admin'); $client = $this->getClient(); $em = $client->getContainer()->get('doctrine.orm.entity_manager'); - $nbClients = $em->getRepository('WallabagApiBundle:Client')->findAll(); + // Try to remove an admin's client with a wrong user + $this->logInAs('bob'); + $client->request('GET', '/developer'); + $this->assertContains('no_client', $client->getResponse()->getContent()); + + // get an ID of a admin's client + $this->logInAs('admin'); + $nbClients = $em->getRepository('WallabagApiBundle:Client')->findByUser($this->getLoggedInUserId()); + + $this->logInAs('bob'); + $client->request('GET', '/developer/client/delete/'.$nbClients[0]->getId()); + $this->assertEquals(403, $client->getResponse()->getStatusCode()); + + // Try to remove the admin's client with the good user + $this->logInAs('admin'); $crawler = $client->request('GET', '/developer'); $link = $crawler @@ -98,7 +111,7 @@ class DeveloperControllerTest extends WallabagCoreTestCase $client->click($link); $this->assertEquals(302, $client->getResponse()->getStatusCode()); - $newNbClients = $em->getRepository('WallabagApiBundle:Client')->findAll(); + $newNbClients = $em->getRepository('WallabagApiBundle:Client')->findByUser($this->getLoggedInUserId()); $this->assertGreaterThan(count($newNbClients), count($nbClients)); } } -- cgit v1.2.3 From f08ec5f88a78bfe2edf2c2148094f3f099e8389c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolas=20L=C5=93uillet?= Date: Fri, 28 Oct 2016 11:11:32 +0200 Subject: Remove backquote in query --- app/DoctrineMigrations/Version20161024212538.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/app/DoctrineMigrations/Version20161024212538.php b/app/DoctrineMigrations/Version20161024212538.php index b9dc500c..f8e927e4 100644 --- a/app/DoctrineMigrations/Version20161024212538.php +++ b/app/DoctrineMigrations/Version20161024212538.php @@ -7,9 +7,6 @@ use Doctrine\DBAL\Schema\Schema; use Symfony\Component\DependencyInjection\ContainerAwareInterface; use Symfony\Component\DependencyInjection\ContainerInterface; -/** - * Auto-generated Migration: Please modify to your needs! - */ class Version20161024212538 extends AbstractMigration implements ContainerAwareInterface { /** @@ -34,7 +31,7 @@ class Version20161024212538 extends AbstractMigration implements ContainerAwareI { $this->skipIf($this->connection->getDatabasePlatform()->getName() == 'sqlite', 'Migration can only be executed safely on \'mysql\' or \'postgresql\'.'); - $this->addSql('ALTER TABLE '.$this->getTable('oauth2_clients').' ADD `user_id` INT(11) DEFAULT NULL'); + $this->addSql('ALTER TABLE '.$this->getTable('oauth2_clients').' ADD user_id INT(11) DEFAULT NULL'); $this->addSql('ALTER TABLE '.$this->getTable('oauth2_clients').' ADD CONSTRAINT FK_clients_user_clients FOREIGN KEY (user_id) REFERENCES '.$this->getTable('user').' (id) ON DELETE CASCADE'); } -- cgit v1.2.3 From 4dface66707688ea440a6a7569795a85631d1ff0 Mon Sep 17 00:00:00 2001 From: Thomas Citharel Date: Sun, 26 Jun 2016 10:27:31 +0200 Subject: first draft (from v1) --- src/Wallabag/CoreBundle/Helper/ContentProxy.php | 168 ++++++++++++++++++++++++ 1 file changed, 168 insertions(+) diff --git a/src/Wallabag/CoreBundle/Helper/ContentProxy.php b/src/Wallabag/CoreBundle/Helper/ContentProxy.php index 8019df42..ddeffa77 100644 --- a/src/Wallabag/CoreBundle/Helper/ContentProxy.php +++ b/src/Wallabag/CoreBundle/Helper/ContentProxy.php @@ -151,4 +151,172 @@ class ContentProxy { return isset($content['title']) && isset($content['html']) && isset($content['url']) && isset($content['language']) && isset($content['content_type']); } + + /** + * Changing pictures URL in article content. + */ + public static function filterPicture($content, $url, $id) + { + $matches = array(); + $processing_pictures = array(); // list of processing image to avoid processing the same pictures twice + preg_match_all('#<\s*(img)[^>]+src="([^"]*)"[^>]*>#Si', $content, $matches, PREG_SET_ORDER); + foreach ($matches as $i => $link) { + $link[1] = trim($link[1]); + if (!preg_match('#^(([a-z]+://)|(\#))#', $link[1])) { + $absolute_path = self::_getAbsoluteLink($link[2], $url); + $filename = basename(parse_url($absolute_path, PHP_URL_PATH)); + $directory = self::_createAssetsDirectory($id); + $fullpath = $directory.'/'.$filename; + + if (in_array($absolute_path, $processing_pictures) === true) { + // replace picture's URL only if processing is OK : already processing -> go to next picture + continue; + } + + if (self::_downloadPictures($absolute_path, $fullpath) === true) { + $content = str_replace($matches[$i][2], Tools::getPocheUrl().$fullpath, $content); + } + + $processing_pictures[] = $absolute_path; + } + } + + return $content; + } + + /** + * Get absolute URL. + */ + private static function _getAbsoluteLink($relativeLink, $url) + { + /* return if already absolute URL */ + if (parse_url($relativeLink, PHP_URL_SCHEME) != '') { + return $relativeLink; + } + + /* queries and anchors */ + if ($relativeLink[0] == '#' || $relativeLink[0] == '?') { + return $url.$relativeLink; + } + + /* parse base URL and convert to local variables: + $scheme, $host, $path */ + extract(parse_url($url)); + + /* remove non-directory element from path */ + $path = preg_replace('#/[^/]*$#', '', $path); + + /* destroy path if relative url points to root */ + if ($relativeLink[0] == '/') { + $path = ''; + } + + /* dirty absolute URL */ + $abs = $host.$path.'/'.$relativeLink; + + /* replace '//' or '/./' or '/foo/../' with '/' */ + $re = array('#(/\.?/)#', '#/(?!\.\.)[^/]+/\.\./#'); + for ($n = 1; $n > 0; $abs = preg_replace($re, '/', $abs, -1, $n)) { + } + + /* absolute URL is ready! */ + return $scheme.'://'.$abs; + } + + /** + * Downloading pictures. + * + * @return bool true if the download and processing is OK, false else + */ + private static function _downloadPictures($absolute_path, $fullpath) + { + $rawdata = Tools::getFile($absolute_path); + $fullpath = urldecode($fullpath); + + if (file_exists($fullpath)) { + unlink($fullpath); + } + + // check extension + $file_ext = strrchr($fullpath, '.'); + $whitelist = array('.jpg', '.jpeg', '.gif', '.png'); + if (!(in_array($file_ext, $whitelist))) { + Tools::logm('processed image with not allowed extension. Skipping '.$fullpath); + + return false; + } + + // check headers + $imageinfo = getimagesize($absolute_path); + if ($imageinfo['mime'] != 'image/gif' && $imageinfo['mime'] != 'image/jpeg' && $imageinfo['mime'] != 'image/jpg' && $imageinfo['mime'] != 'image/png') { + Tools::logm('processed image with bad header. Skipping '.$fullpath); + + return false; + } + + // regenerate image + $im = imagecreatefromstring($rawdata); + if ($im === false) { + Tools::logm('error while regenerating image '.$fullpath); + + return false; + } + + switch ($imageinfo['mime']) { + case 'image/gif': + $result = imagegif($im, $fullpath); + break; + case 'image/jpeg': + case 'image/jpg': + $result = imagejpeg($im, $fullpath, REGENERATE_PICTURES_QUALITY); + break; + case 'image/png': + $result = imagepng($im, $fullpath, ceil(REGENERATE_PICTURES_QUALITY / 100 * 9)); + break; + } + imagedestroy($im); + + return $result; + } + + /** + * Create a directory for an article. + * + * @param $id ID of the article + * + * @return string + */ + private static function _createAssetsDirectory($id) + { + $assets_path = ABS_PATH; + if (!is_dir($assets_path)) { + mkdir($assets_path, 0715); + } + + $article_directory = $assets_path.$id; + if (!is_dir($article_directory)) { + mkdir($article_directory, 0715); + } + + return $article_directory; + } + + /** + * Remove the directory. + * + * @param $directory + * + * @return bool + */ + public static function removeDirectory($directory) + { + if (is_dir($directory)) { + $files = array_diff(scandir($directory), array('.', '..')); + foreach ($files as $file) { + (is_dir("$directory/$file")) ? self::removeDirectory("$directory/$file") : unlink("$directory/$file"); + } + + return rmdir($directory); + } + } } -- cgit v1.2.3 From 419214d7221e0821ef2b73eb2b3db816ed0cf173 Mon Sep 17 00:00:00 2001 From: Thomas Citharel Date: Tue, 28 Jun 2016 19:07:55 +0200 Subject: Download pictures successfully Needs to rewrite them properly (get base url) --- composer.json | 3 +- src/Wallabag/CoreBundle/Helper/ContentProxy.php | 178 ++-------------------- src/Wallabag/CoreBundle/Helper/DownloadImages.php | 147 ++++++++++++++++++ 3 files changed, 158 insertions(+), 170 deletions(-) create mode 100644 src/Wallabag/CoreBundle/Helper/DownloadImages.php diff --git a/composer.json b/composer.json index ebc0a7dc..e7b30fa1 100644 --- a/composer.json +++ b/composer.json @@ -82,7 +82,8 @@ "white-october/pagerfanta-bundle": "^1.0", "php-amqplib/rabbitmq-bundle": "^1.8", "predis/predis": "^1.0", - "javibravo/simpleue": "^1.0" + "javibravo/simpleue": "^1.0", + "symfony/dom-crawler": "^3.1" }, "require-dev": { "doctrine/doctrine-fixtures-bundle": "~2.2", diff --git a/src/Wallabag/CoreBundle/Helper/ContentProxy.php b/src/Wallabag/CoreBundle/Helper/ContentProxy.php index ddeffa77..bbad705f 100644 --- a/src/Wallabag/CoreBundle/Helper/ContentProxy.php +++ b/src/Wallabag/CoreBundle/Helper/ContentProxy.php @@ -65,7 +65,7 @@ class ContentProxy $entry->setUrl($content['url'] ?: $url); $entry->setTitle($title); - $entry->setContent($html); + $entry->setLanguage($content['language']); $entry->setMimetype($content['content_type']); $entry->setReadingTime(Utils::getReadingTime($html)); @@ -75,6 +75,14 @@ class ContentProxy $entry->setDomainName($domainName); } + if (true) { + $this->logger->log('debug','Starting to download images'); + $downloadImages = new DownloadImages($html, $url, $this->logger); + $html = $downloadImages->process(); + } + + $entry->setContent($html); + if (isset($content['open_graph']['og_image'])) { $entry->setPreviewPicture($content['open_graph']['og_image']); } @@ -151,172 +159,4 @@ class ContentProxy { return isset($content['title']) && isset($content['html']) && isset($content['url']) && isset($content['language']) && isset($content['content_type']); } - - /** - * Changing pictures URL in article content. - */ - public static function filterPicture($content, $url, $id) - { - $matches = array(); - $processing_pictures = array(); // list of processing image to avoid processing the same pictures twice - preg_match_all('#<\s*(img)[^>]+src="([^"]*)"[^>]*>#Si', $content, $matches, PREG_SET_ORDER); - foreach ($matches as $i => $link) { - $link[1] = trim($link[1]); - if (!preg_match('#^(([a-z]+://)|(\#))#', $link[1])) { - $absolute_path = self::_getAbsoluteLink($link[2], $url); - $filename = basename(parse_url($absolute_path, PHP_URL_PATH)); - $directory = self::_createAssetsDirectory($id); - $fullpath = $directory.'/'.$filename; - - if (in_array($absolute_path, $processing_pictures) === true) { - // replace picture's URL only if processing is OK : already processing -> go to next picture - continue; - } - - if (self::_downloadPictures($absolute_path, $fullpath) === true) { - $content = str_replace($matches[$i][2], Tools::getPocheUrl().$fullpath, $content); - } - - $processing_pictures[] = $absolute_path; - } - } - - return $content; - } - - /** - * Get absolute URL. - */ - private static function _getAbsoluteLink($relativeLink, $url) - { - /* return if already absolute URL */ - if (parse_url($relativeLink, PHP_URL_SCHEME) != '') { - return $relativeLink; - } - - /* queries and anchors */ - if ($relativeLink[0] == '#' || $relativeLink[0] == '?') { - return $url.$relativeLink; - } - - /* parse base URL and convert to local variables: - $scheme, $host, $path */ - extract(parse_url($url)); - - /* remove non-directory element from path */ - $path = preg_replace('#/[^/]*$#', '', $path); - - /* destroy path if relative url points to root */ - if ($relativeLink[0] == '/') { - $path = ''; - } - - /* dirty absolute URL */ - $abs = $host.$path.'/'.$relativeLink; - - /* replace '//' or '/./' or '/foo/../' with '/' */ - $re = array('#(/\.?/)#', '#/(?!\.\.)[^/]+/\.\./#'); - for ($n = 1; $n > 0; $abs = preg_replace($re, '/', $abs, -1, $n)) { - } - - /* absolute URL is ready! */ - return $scheme.'://'.$abs; - } - - /** - * Downloading pictures. - * - * @return bool true if the download and processing is OK, false else - */ - private static function _downloadPictures($absolute_path, $fullpath) - { - $rawdata = Tools::getFile($absolute_path); - $fullpath = urldecode($fullpath); - - if (file_exists($fullpath)) { - unlink($fullpath); - } - - // check extension - $file_ext = strrchr($fullpath, '.'); - $whitelist = array('.jpg', '.jpeg', '.gif', '.png'); - if (!(in_array($file_ext, $whitelist))) { - Tools::logm('processed image with not allowed extension. Skipping '.$fullpath); - - return false; - } - - // check headers - $imageinfo = getimagesize($absolute_path); - if ($imageinfo['mime'] != 'image/gif' && $imageinfo['mime'] != 'image/jpeg' && $imageinfo['mime'] != 'image/jpg' && $imageinfo['mime'] != 'image/png') { - Tools::logm('processed image with bad header. Skipping '.$fullpath); - - return false; - } - - // regenerate image - $im = imagecreatefromstring($rawdata); - if ($im === false) { - Tools::logm('error while regenerating image '.$fullpath); - - return false; - } - - switch ($imageinfo['mime']) { - case 'image/gif': - $result = imagegif($im, $fullpath); - break; - case 'image/jpeg': - case 'image/jpg': - $result = imagejpeg($im, $fullpath, REGENERATE_PICTURES_QUALITY); - break; - case 'image/png': - $result = imagepng($im, $fullpath, ceil(REGENERATE_PICTURES_QUALITY / 100 * 9)); - break; - } - imagedestroy($im); - - return $result; - } - - /** - * Create a directory for an article. - * - * @param $id ID of the article - * - * @return string - */ - private static function _createAssetsDirectory($id) - { - $assets_path = ABS_PATH; - if (!is_dir($assets_path)) { - mkdir($assets_path, 0715); - } - - $article_directory = $assets_path.$id; - if (!is_dir($article_directory)) { - mkdir($article_directory, 0715); - } - - return $article_directory; - } - - /** - * Remove the directory. - * - * @param $directory - * - * @return bool - */ - public static function removeDirectory($directory) - { - if (is_dir($directory)) { - $files = array_diff(scandir($directory), array('.', '..')); - foreach ($files as $file) { - (is_dir("$directory/$file")) ? self::removeDirectory("$directory/$file") : unlink("$directory/$file"); - } - - return rmdir($directory); - } - } } diff --git a/src/Wallabag/CoreBundle/Helper/DownloadImages.php b/src/Wallabag/CoreBundle/Helper/DownloadImages.php new file mode 100644 index 00000000..32a9dbb2 --- /dev/null +++ b/src/Wallabag/CoreBundle/Helper/DownloadImages.php @@ -0,0 +1,147 @@ +html = $html; + $this->url = $url; + $this->setFolder(); + $this->logger = $logger; + } + + public function setFolder($folder = "assets/images") { + // if folder doesn't exist, attempt to create one and store the folder name in property $folder + if(!file_exists($folder)) { + mkdir($folder); + } + $this->folder = $folder; + } + + public function process() { + //instantiate the symfony DomCrawler Component + $crawler = new Crawler($this->html); + // create an array of all scrapped image links + $this->logger->log('debug', 'Finding images inside document'); + $result = $crawler + ->filterXpath('//img') + ->extract(array('src')); + + // download and save the image to the folder + foreach ($result as $image) { + $file = file_get_contents($image); + + // Checks + $absolute_path = self::getAbsoluteLink($image, $this->url); + $filename = basename(parse_url($absolute_path, PHP_URL_PATH)); + $fullpath = $this->folder."/".$filename; + self::checks($file, $fullpath, $absolute_path); + $this->html = str_replace($image, $fullpath, $this->html); + } + + return $this->html; + } + + private function checks($rawdata, $fullpath, $absolute_path) { + $fullpath = urldecode($fullpath); + + if (file_exists($fullpath)) { + unlink($fullpath); + } + + // check extension + $this->logger->log('debug','Checking extension'); + + $file_ext = strrchr($fullpath, '.'); + $whitelist = array('.jpg', '.jpeg', '.gif', '.png'); + if (!(in_array($file_ext, $whitelist))) { + $this->logger->log('debug','processed image with not allowed extension. Skipping '.$fullpath); + + return false; + } + + // check headers + $this->logger->log('debug','Checking headers'); + $imageinfo = getimagesize($absolute_path); + if ($imageinfo['mime'] != 'image/gif' && $imageinfo['mime'] != 'image/jpeg' && $imageinfo['mime'] != 'image/jpg' && $imageinfo['mime'] != 'image/png') { + $this->logger->log('debug','processed image with bad header. Skipping '.$fullpath); + + return false; + } + + // regenerate image + $this->logger->log('debug','regenerating image'); + $im = imagecreatefromstring($rawdata); + if ($im === false) { + $this->logger->log('error','error while regenerating image '.$fullpath); + + return false; + } + + switch ($imageinfo['mime']) { + case 'image/gif': + $result = imagegif($im, $fullpath); + $this->logger->log('debug','Re-creating gif'); + break; + case 'image/jpeg': + case 'image/jpg': + $result = imagejpeg($im, $fullpath, REGENERATE_PICTURES_QUALITY); + $this->logger->log('debug','Re-creating jpg'); + break; + case 'image/png': + $this->logger->log('debug','Re-creating png'); + $result = imagepng($im, $fullpath, ceil(REGENERATE_PICTURES_QUALITY / 100 * 9)); + break; + } + imagedestroy($im); + + return $result; + } + + private static function getAbsoluteLink($relativeLink, $url) + { + /* return if already absolute URL */ + if (parse_url($relativeLink, PHP_URL_SCHEME) != '') { + return $relativeLink; + } + + /* queries and anchors */ + if ($relativeLink[0] == '#' || $relativeLink[0] == '?') { + return $url.$relativeLink; + } + + /* parse base URL and convert to local variables: + $scheme, $host, $path */ + extract(parse_url($url)); + + /* remove non-directory element from path */ + $path = preg_replace('#/[^/]*$#', '', $path); + + /* destroy path if relative url points to root */ + if ($relativeLink[0] == '/') { + $path = ''; + } + + /* dirty absolute URL */ + $abs = $host.$path.'/'.$relativeLink; + + /* replace '//' or '/./' or '/foo/../' with '/' */ + $re = array('#(/\.?/)#', '#/(?!\.\.)[^/]+/\.\./#'); + for ($n = 1; $n > 0; $abs = preg_replace($re, '/', $abs, -1, $n)) { + } + + /* absolute URL is ready! */ + return $scheme.'://'.$abs; + } +} -- cgit v1.2.3 From 94654765cca6771c2f54eeaa056b7e65f3353105 Mon Sep 17 00:00:00 2001 From: Thomas Citharel Date: Tue, 28 Jun 2016 22:06:00 +0200 Subject: Working --- src/Wallabag/CoreBundle/Helper/DownloadImages.php | 44 ++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/src/Wallabag/CoreBundle/Helper/DownloadImages.php b/src/Wallabag/CoreBundle/Helper/DownloadImages.php index 32a9dbb2..14f0aa1b 100644 --- a/src/Wallabag/CoreBundle/Helper/DownloadImages.php +++ b/src/Wallabag/CoreBundle/Helper/DownloadImages.php @@ -6,6 +6,9 @@ use Psr\Log\LoggerInterface as Logger; use Symfony\Component\DomCrawler\Crawler; define('REGENERATE_PICTURES_QUALITY', 75); +define('HTTP_PORT', 80); +define('SSL_PORT', 443); +define('BASE_URL',''); class DownloadImages { private $folder; @@ -47,7 +50,7 @@ class DownloadImages { $filename = basename(parse_url($absolute_path, PHP_URL_PATH)); $fullpath = $this->folder."/".$filename; self::checks($file, $fullpath, $absolute_path); - $this->html = str_replace($image, $fullpath, $this->html); + $this->html = str_replace($image, self::getPocheUrl() . '/' . $fullpath, $this->html); } return $this->html; @@ -144,4 +147,43 @@ class DownloadImages { /* absolute URL is ready! */ return $scheme.'://'.$abs; } + + public static function getPocheUrl() + { + $baseUrl = ""; + $https = (!empty($_SERVER['HTTPS']) + && (strtolower($_SERVER['HTTPS']) == 'on')) + || (isset($_SERVER["SERVER_PORT"]) + && $_SERVER["SERVER_PORT"] == '443') // HTTPS detection. + || (isset($_SERVER["SERVER_PORT"]) //Custom HTTPS port detection + && $_SERVER["SERVER_PORT"] == SSL_PORT) + || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) + && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https'); + $serverport = (!isset($_SERVER["SERVER_PORT"]) + || $_SERVER["SERVER_PORT"] == '80' + || $_SERVER["SERVER_PORT"] == HTTP_PORT + || ($https && $_SERVER["SERVER_PORT"] == '443') + || ($https && $_SERVER["SERVER_PORT"]==SSL_PORT) //Custom HTTPS port detection + ? '' : ':' . $_SERVER["SERVER_PORT"]); + + if (isset($_SERVER["HTTP_X_FORWARDED_PORT"])) { + $serverport = ':' . $_SERVER["HTTP_X_FORWARDED_PORT"]; + } + // $scriptname = str_replace('/index.php', '/', $_SERVER["SCRIPT_NAME"]); + // if (!isset($_SERVER["HTTP_HOST"])) { + // return $scriptname; + // } + $host = (isset($_SERVER['HTTP_X_FORWARDED_HOST']) ? $_SERVER['HTTP_X_FORWARDED_HOST'] : (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : $_SERVER['SERVER_NAME'])); + if (strpos($host, ':') !== false) { + $serverport = ''; + } + // check if BASE_URL is configured + if(BASE_URL) { + $baseUrl = BASE_URL; + } else { + $baseUrl = 'http' . ($https ? 's' : '') . '://' . $host . $serverport; + } + return $baseUrl; + + } } -- cgit v1.2.3 From 156bf62758080153668a65db611c4241d0fc8a00 Mon Sep 17 00:00:00 2001 From: Jeremy Benoist Date: Sat, 22 Oct 2016 09:22:30 +0200 Subject: CS --- src/Wallabag/CoreBundle/Helper/ContentProxy.php | 2 +- src/Wallabag/CoreBundle/Helper/DownloadImages.php | 77 ++++++++++++----------- 2 files changed, 42 insertions(+), 37 deletions(-) diff --git a/src/Wallabag/CoreBundle/Helper/ContentProxy.php b/src/Wallabag/CoreBundle/Helper/ContentProxy.php index bbad705f..8ed11205 100644 --- a/src/Wallabag/CoreBundle/Helper/ContentProxy.php +++ b/src/Wallabag/CoreBundle/Helper/ContentProxy.php @@ -76,7 +76,7 @@ class ContentProxy } if (true) { - $this->logger->log('debug','Starting to download images'); + $this->logger->log('debug', 'Starting to download images'); $downloadImages = new DownloadImages($html, $url, $this->logger); $html = $downloadImages->process(); } diff --git a/src/Wallabag/CoreBundle/Helper/DownloadImages.php b/src/Wallabag/CoreBundle/Helper/DownloadImages.php index 14f0aa1b..e23e0c55 100644 --- a/src/Wallabag/CoreBundle/Helper/DownloadImages.php +++ b/src/Wallabag/CoreBundle/Helper/DownloadImages.php @@ -8,31 +8,35 @@ use Symfony\Component\DomCrawler\Crawler; define('REGENERATE_PICTURES_QUALITY', 75); define('HTTP_PORT', 80); define('SSL_PORT', 443); -define('BASE_URL',''); +define('BASE_URL', ''); -class DownloadImages { +class DownloadImages +{ private $folder; private $url; private $html; private $fileName; private $logger; - public function __construct($html, $url, Logger $logger) { + public function __construct($html, $url, Logger $logger) + { $this->html = $html; $this->url = $url; $this->setFolder(); $this->logger = $logger; } - public function setFolder($folder = "assets/images") { + public function setFolder($folder = 'assets/images') + { // if folder doesn't exist, attempt to create one and store the folder name in property $folder - if(!file_exists($folder)) { + if (!file_exists($folder)) { mkdir($folder); } $this->folder = $folder; } - public function process() { + public function process() + { //instantiate the symfony DomCrawler Component $crawler = new Crawler($this->html); // create an array of all scrapped image links @@ -48,15 +52,16 @@ class DownloadImages { // Checks $absolute_path = self::getAbsoluteLink($image, $this->url); $filename = basename(parse_url($absolute_path, PHP_URL_PATH)); - $fullpath = $this->folder."/".$filename; + $fullpath = $this->folder.'/'.$filename; self::checks($file, $fullpath, $absolute_path); - $this->html = str_replace($image, self::getPocheUrl() . '/' . $fullpath, $this->html); + $this->html = str_replace($image, self::getPocheUrl().'/'.$fullpath, $this->html); } return $this->html; } - private function checks($rawdata, $fullpath, $absolute_path) { + private function checks($rawdata, $fullpath, $absolute_path) + { $fullpath = urldecode($fullpath); if (file_exists($fullpath)) { @@ -64,30 +69,30 @@ class DownloadImages { } // check extension - $this->logger->log('debug','Checking extension'); + $this->logger->log('debug', 'Checking extension'); $file_ext = strrchr($fullpath, '.'); $whitelist = array('.jpg', '.jpeg', '.gif', '.png'); if (!(in_array($file_ext, $whitelist))) { - $this->logger->log('debug','processed image with not allowed extension. Skipping '.$fullpath); + $this->logger->log('debug', 'processed image with not allowed extension. Skipping '.$fullpath); return false; } // check headers - $this->logger->log('debug','Checking headers'); + $this->logger->log('debug', 'Checking headers'); $imageinfo = getimagesize($absolute_path); if ($imageinfo['mime'] != 'image/gif' && $imageinfo['mime'] != 'image/jpeg' && $imageinfo['mime'] != 'image/jpg' && $imageinfo['mime'] != 'image/png') { - $this->logger->log('debug','processed image with bad header. Skipping '.$fullpath); + $this->logger->log('debug', 'processed image with bad header. Skipping '.$fullpath); return false; } // regenerate image - $this->logger->log('debug','regenerating image'); + $this->logger->log('debug', 'regenerating image'); $im = imagecreatefromstring($rawdata); if ($im === false) { - $this->logger->log('error','error while regenerating image '.$fullpath); + $this->logger->log('error', 'error while regenerating image '.$fullpath); return false; } @@ -95,15 +100,15 @@ class DownloadImages { switch ($imageinfo['mime']) { case 'image/gif': $result = imagegif($im, $fullpath); - $this->logger->log('debug','Re-creating gif'); + $this->logger->log('debug', 'Re-creating gif'); break; case 'image/jpeg': case 'image/jpg': $result = imagejpeg($im, $fullpath, REGENERATE_PICTURES_QUALITY); - $this->logger->log('debug','Re-creating jpg'); + $this->logger->log('debug', 'Re-creating jpg'); break; case 'image/png': - $this->logger->log('debug','Re-creating png'); + $this->logger->log('debug', 'Re-creating png'); $result = imagepng($im, $fullpath, ceil(REGENERATE_PICTURES_QUALITY / 100 * 9)); break; } @@ -150,24 +155,24 @@ class DownloadImages { public static function getPocheUrl() { - $baseUrl = ""; + $baseUrl = ''; $https = (!empty($_SERVER['HTTPS']) && (strtolower($_SERVER['HTTPS']) == 'on')) - || (isset($_SERVER["SERVER_PORT"]) - && $_SERVER["SERVER_PORT"] == '443') // HTTPS detection. - || (isset($_SERVER["SERVER_PORT"]) //Custom HTTPS port detection - && $_SERVER["SERVER_PORT"] == SSL_PORT) + || (isset($_SERVER['SERVER_PORT']) + && $_SERVER['SERVER_PORT'] == '443') // HTTPS detection. + || (isset($_SERVER['SERVER_PORT']) //Custom HTTPS port detection + && $_SERVER['SERVER_PORT'] == SSL_PORT) || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https'); - $serverport = (!isset($_SERVER["SERVER_PORT"]) - || $_SERVER["SERVER_PORT"] == '80' - || $_SERVER["SERVER_PORT"] == HTTP_PORT - || ($https && $_SERVER["SERVER_PORT"] == '443') - || ($https && $_SERVER["SERVER_PORT"]==SSL_PORT) //Custom HTTPS port detection - ? '' : ':' . $_SERVER["SERVER_PORT"]); - - if (isset($_SERVER["HTTP_X_FORWARDED_PORT"])) { - $serverport = ':' . $_SERVER["HTTP_X_FORWARDED_PORT"]; + $serverport = (!isset($_SERVER['SERVER_PORT']) + || $_SERVER['SERVER_PORT'] == '80' + || $_SERVER['SERVER_PORT'] == HTTP_PORT + || ($https && $_SERVER['SERVER_PORT'] == '443') + || ($https && $_SERVER['SERVER_PORT'] == SSL_PORT) //Custom HTTPS port detection + ? '' : ':'.$_SERVER['SERVER_PORT']); + + if (isset($_SERVER['HTTP_X_FORWARDED_PORT'])) { + $serverport = ':'.$_SERVER['HTTP_X_FORWARDED_PORT']; } // $scriptname = str_replace('/index.php', '/', $_SERVER["SCRIPT_NAME"]); // if (!isset($_SERVER["HTTP_HOST"])) { @@ -178,12 +183,12 @@ class DownloadImages { $serverport = ''; } // check if BASE_URL is configured - if(BASE_URL) { + if (BASE_URL) { $baseUrl = BASE_URL; } else { - $baseUrl = 'http' . ($https ? 's' : '') . '://' . $host . $serverport; + $baseUrl = 'http'.($https ? 's' : '').'://'.$host.$serverport; } - return $baseUrl; - + + return $baseUrl; } } -- cgit v1.2.3 From 535bfcbe80de5d697b768c3a657214fdeff0eac3 Mon Sep 17 00:00:00 2001 From: Jeremy Benoist Date: Sun, 30 Oct 2016 09:58:39 +0100 Subject: Move related event things in Event folder --- .gitignore | 3 +- app/config/services.yml | 4 +- data/assets/.gitignore | 0 .../CoreBundle/Event/Listener/LocaleListener.php | 44 ++++++++++++++ .../Event/Listener/UserLocaleListener.php | 37 ++++++++++++ .../Subscriber/SQLiteCascadeDeleteSubscriber.php | 70 ++++++++++++++++++++++ .../Event/Subscriber/TablePrefixSubscriber.php | 51 ++++++++++++++++ .../CoreBundle/EventListener/LocaleListener.php | 44 -------------- .../EventListener/UserLocaleListener.php | 37 ------------ .../CoreBundle/Resources/config/services.yml | 4 +- .../Subscriber/SQLiteCascadeDeleteSubscriber.php | 70 ---------------------- .../Subscriber/TablePrefixSubscriber.php | 51 ---------------- web/assets/images/.gitkeep | 0 13 files changed, 208 insertions(+), 207 deletions(-) delete mode 100644 data/assets/.gitignore create mode 100644 src/Wallabag/CoreBundle/Event/Listener/LocaleListener.php create mode 100644 src/Wallabag/CoreBundle/Event/Listener/UserLocaleListener.php create mode 100644 src/Wallabag/CoreBundle/Event/Subscriber/SQLiteCascadeDeleteSubscriber.php create mode 100644 src/Wallabag/CoreBundle/Event/Subscriber/TablePrefixSubscriber.php delete mode 100644 src/Wallabag/CoreBundle/EventListener/LocaleListener.php delete mode 100644 src/Wallabag/CoreBundle/EventListener/UserLocaleListener.php delete mode 100644 src/Wallabag/CoreBundle/Subscriber/SQLiteCascadeDeleteSubscriber.php delete mode 100644 src/Wallabag/CoreBundle/Subscriber/TablePrefixSubscriber.php create mode 100644 web/assets/images/.gitkeep diff --git a/.gitignore b/.gitignore index 32b0fbbb..84fb95d7 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,8 @@ web/uploads/ !web/bundles web/bundles/* !web/bundles/wallabagcore +/web/assets/images/* +!web/assets/images/.gitkeep # Build /app/build @@ -34,7 +36,6 @@ web/bundles/* /composer.phar # Data for wallabag -data/assets/* data/db/wallabag*.sqlite # Docker container logs and data diff --git a/app/config/services.yml b/app/config/services.yml index a57ef0f3..9a1ce80b 100644 --- a/app/config/services.yml +++ b/app/config/services.yml @@ -32,13 +32,13 @@ services: - { name: twig.extension } wallabag.locale_listener: - class: Wallabag\CoreBundle\EventListener\LocaleListener + class: Wallabag\CoreBundle\Event\Listener\LocaleListener arguments: ["%kernel.default_locale%"] tags: - { name: kernel.event_subscriber } wallabag.user_locale_listener: - class: Wallabag\CoreBundle\EventListener\UserLocaleListener + class: Wallabag\CoreBundle\Event\Listener\UserLocaleListener arguments: ["@session"] tags: - { name: kernel.event_listener, event: security.interactive_login, method: onInteractiveLogin } diff --git a/data/assets/.gitignore b/data/assets/.gitignore deleted file mode 100644 index e69de29b..00000000 diff --git a/src/Wallabag/CoreBundle/Event/Listener/LocaleListener.php b/src/Wallabag/CoreBundle/Event/Listener/LocaleListener.php new file mode 100644 index 00000000..b435d99e --- /dev/null +++ b/src/Wallabag/CoreBundle/Event/Listener/LocaleListener.php @@ -0,0 +1,44 @@ +defaultLocale = $defaultLocale; + } + + public function onKernelRequest(GetResponseEvent $event) + { + $request = $event->getRequest(); + if (!$request->hasPreviousSession()) { + return; + } + + // try to see if the locale has been set as a _locale routing parameter + if ($locale = $request->attributes->get('_locale')) { + $request->getSession()->set('_locale', $locale); + } else { + // if no explicit locale has been set on this request, use one from the session + $request->setLocale($request->getSession()->get('_locale', $this->defaultLocale)); + } + } + + public static function getSubscribedEvents() + { + return [ + // must be registered before the default Locale listener + KernelEvents::REQUEST => [['onKernelRequest', 17]], + ]; + } +} diff --git a/src/Wallabag/CoreBundle/Event/Listener/UserLocaleListener.php b/src/Wallabag/CoreBundle/Event/Listener/UserLocaleListener.php new file mode 100644 index 00000000..367cdfb0 --- /dev/null +++ b/src/Wallabag/CoreBundle/Event/Listener/UserLocaleListener.php @@ -0,0 +1,37 @@ +session = $session; + } + + /** + * @param InteractiveLoginEvent $event + */ + public function onInteractiveLogin(InteractiveLoginEvent $event) + { + $user = $event->getAuthenticationToken()->getUser(); + + if (null !== $user->getConfig()->getLanguage()) { + $this->session->set('_locale', $user->getConfig()->getLanguage()); + } + } +} diff --git a/src/Wallabag/CoreBundle/Event/Subscriber/SQLiteCascadeDeleteSubscriber.php b/src/Wallabag/CoreBundle/Event/Subscriber/SQLiteCascadeDeleteSubscriber.php new file mode 100644 index 00000000..3b4c4cf9 --- /dev/null +++ b/src/Wallabag/CoreBundle/Event/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/CoreBundle/Event/Subscriber/TablePrefixSubscriber.php b/src/Wallabag/CoreBundle/Event/Subscriber/TablePrefixSubscriber.php new file mode 100644 index 00000000..9013328f --- /dev/null +++ b/src/Wallabag/CoreBundle/Event/Subscriber/TablePrefixSubscriber.php @@ -0,0 +1,51 @@ +prefix = (string) $prefix; + } + + public function getSubscribedEvents() + { + return ['loadClassMetadata']; + } + + public function loadClassMetadata(LoadClassMetadataEventArgs $args) + { + $classMetadata = $args->getClassMetadata(); + + // if we are in an inheritance hierarchy, only apply this once + if ($classMetadata->isInheritanceTypeSingleTable() && !$classMetadata->isRootEntity()) { + return; + } + + $classMetadata->setTableName($this->prefix.$classMetadata->getTableName()); + + foreach ($classMetadata->getAssociationMappings() as $fieldName => $mapping) { + if ($mapping['type'] === ClassMetadataInfo::MANY_TO_MANY && isset($classMetadata->associationMappings[$fieldName]['joinTable']['name'])) { + $mappedTableName = $classMetadata->associationMappings[$fieldName]['joinTable']['name']; + $classMetadata->associationMappings[$fieldName]['joinTable']['name'] = $this->prefix.$mappedTableName; + } + } + } +} diff --git a/src/Wallabag/CoreBundle/EventListener/LocaleListener.php b/src/Wallabag/CoreBundle/EventListener/LocaleListener.php deleted file mode 100644 index a1c7e5ab..00000000 --- a/src/Wallabag/CoreBundle/EventListener/LocaleListener.php +++ /dev/null @@ -1,44 +0,0 @@ -defaultLocale = $defaultLocale; - } - - public function onKernelRequest(GetResponseEvent $event) - { - $request = $event->getRequest(); - if (!$request->hasPreviousSession()) { - return; - } - - // try to see if the locale has been set as a _locale routing parameter - if ($locale = $request->attributes->get('_locale')) { - $request->getSession()->set('_locale', $locale); - } else { - // if no explicit locale has been set on this request, use one from the session - $request->setLocale($request->getSession()->get('_locale', $this->defaultLocale)); - } - } - - public static function getSubscribedEvents() - { - return [ - // must be registered before the default Locale listener - KernelEvents::REQUEST => [['onKernelRequest', 17]], - ]; - } -} diff --git a/src/Wallabag/CoreBundle/EventListener/UserLocaleListener.php b/src/Wallabag/CoreBundle/EventListener/UserLocaleListener.php deleted file mode 100644 index 82d1a63a..00000000 --- a/src/Wallabag/CoreBundle/EventListener/UserLocaleListener.php +++ /dev/null @@ -1,37 +0,0 @@ -session = $session; - } - - /** - * @param InteractiveLoginEvent $event - */ - public function onInteractiveLogin(InteractiveLoginEvent $event) - { - $user = $event->getAuthenticationToken()->getUser(); - - if (null !== $user->getConfig()->getLanguage()) { - $this->session->set('_locale', $user->getConfig()->getLanguage()); - } - } -} diff --git a/src/Wallabag/CoreBundle/Resources/config/services.yml b/src/Wallabag/CoreBundle/Resources/config/services.yml index cc5f9e9a..4b7751fe 100644 --- a/src/Wallabag/CoreBundle/Resources/config/services.yml +++ b/src/Wallabag/CoreBundle/Resources/config/services.yml @@ -30,7 +30,7 @@ services: - "@doctrine" wallabag_core.subscriber.table_prefix: - class: Wallabag\CoreBundle\Subscriber\TablePrefixSubscriber + class: Wallabag\CoreBundle\Event\Subscriber\TablePrefixSubscriber arguments: - "%database_table_prefix%" tags: @@ -131,7 +131,7 @@ services: - '%kernel.debug%' wallabag_core.subscriber.sqlite_cascade_delete: - class: Wallabag\CoreBundle\Subscriber\SQLiteCascadeDeleteSubscriber + class: Wallabag\CoreBundle\Event\Subscriber\SQLiteCascadeDeleteSubscriber arguments: - "@doctrine" tags: diff --git a/src/Wallabag/CoreBundle/Subscriber/SQLiteCascadeDeleteSubscriber.php b/src/Wallabag/CoreBundle/Subscriber/SQLiteCascadeDeleteSubscriber.php deleted file mode 100644 index f7210bd3..00000000 --- a/src/Wallabag/CoreBundle/Subscriber/SQLiteCascadeDeleteSubscriber.php +++ /dev/null @@ -1,70 +0,0 @@ -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/CoreBundle/Subscriber/TablePrefixSubscriber.php b/src/Wallabag/CoreBundle/Subscriber/TablePrefixSubscriber.php deleted file mode 100644 index 0379ad6a..00000000 --- a/src/Wallabag/CoreBundle/Subscriber/TablePrefixSubscriber.php +++ /dev/null @@ -1,51 +0,0 @@ -prefix = (string) $prefix; - } - - public function getSubscribedEvents() - { - return ['loadClassMetadata']; - } - - public function loadClassMetadata(LoadClassMetadataEventArgs $args) - { - $classMetadata = $args->getClassMetadata(); - - // if we are in an inheritance hierarchy, only apply this once - if ($classMetadata->isInheritanceTypeSingleTable() && !$classMetadata->isRootEntity()) { - return; - } - - $classMetadata->setTableName($this->prefix.$classMetadata->getTableName()); - - foreach ($classMetadata->getAssociationMappings() as $fieldName => $mapping) { - if ($mapping['type'] === ClassMetadataInfo::MANY_TO_MANY && isset($classMetadata->associationMappings[$fieldName]['joinTable']['name'])) { - $mappedTableName = $classMetadata->associationMappings[$fieldName]['joinTable']['name']; - $classMetadata->associationMappings[$fieldName]['joinTable']['name'] = $this->prefix.$mappedTableName; - } - } - } -} diff --git a/web/assets/images/.gitkeep b/web/assets/images/.gitkeep new file mode 100644 index 00000000..e69de29b -- cgit v1.2.3 From 45fd7e09d75995bd0b9a731ffd70054b7ae6ee1f Mon Sep 17 00:00:00 2001 From: Jeremy Benoist Date: Sun, 30 Oct 2016 09:58:53 +0100 Subject: Cleanup --- src/Wallabag/CoreBundle/Helper/ContentProxy.php | 4 ++-- src/Wallabag/ImportBundle/Resources/config/services.yml | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Wallabag/CoreBundle/Helper/ContentProxy.php b/src/Wallabag/CoreBundle/Helper/ContentProxy.php index 8ed11205..219b90d3 100644 --- a/src/Wallabag/CoreBundle/Helper/ContentProxy.php +++ b/src/Wallabag/CoreBundle/Helper/ContentProxy.php @@ -3,7 +3,7 @@ namespace Wallabag\CoreBundle\Helper; use Graby\Graby; -use Psr\Log\LoggerInterface as Logger; +use Psr\Log\LoggerInterface; use Wallabag\CoreBundle\Entity\Entry; use Wallabag\CoreBundle\Entity\Tag; use Wallabag\CoreBundle\Tools\Utils; @@ -20,7 +20,7 @@ class ContentProxy protected $logger; protected $tagRepository; - public function __construct(Graby $graby, RuleBasedTagger $tagger, TagRepository $tagRepository, Logger $logger) + public function __construct(Graby $graby, RuleBasedTagger $tagger, TagRepository $tagRepository, LoggerInterface $logger) { $this->graby = $graby; $this->tagger = $tagger; diff --git a/src/Wallabag/ImportBundle/Resources/config/services.yml b/src/Wallabag/ImportBundle/Resources/config/services.yml index 89adc71b..d600be0f 100644 --- a/src/Wallabag/ImportBundle/Resources/config/services.yml +++ b/src/Wallabag/ImportBundle/Resources/config/services.yml @@ -20,7 +20,6 @@ services: arguments: - "@doctrine.orm.entity_manager" - "@wallabag_core.content_proxy" - - "@craue_config" calls: - [ setClient, [ "@wallabag_import.pocket.client" ] ] - [ setLogger, [ "@logger" ]] -- cgit v1.2.3 From 7f55941856549a3f5f45c42fdc171d66ff7ee297 Mon Sep 17 00:00:00 2001 From: Jeremy Benoist Date: Sun, 30 Oct 2016 10:48:29 +0100 Subject: Use doctrine event to download images --- .../Event/Subscriber/DownloadImagesSubscriber.php | 129 +++++++++++ src/Wallabag/CoreBundle/Helper/ContentProxy.php | 6 - src/Wallabag/CoreBundle/Helper/DownloadImages.php | 248 +++++++++++---------- .../CoreBundle/Resources/config/services.yml | 19 ++ .../CoreBundle/Helper/DownloadImagesTest.php | 123 ++++++++++ tests/Wallabag/CoreBundle/fixtures/unnamed.png | Bin 0 -> 3688 bytes 6 files changed, 397 insertions(+), 128 deletions(-) create mode 100644 src/Wallabag/CoreBundle/Event/Subscriber/DownloadImagesSubscriber.php create mode 100644 tests/Wallabag/CoreBundle/Helper/DownloadImagesTest.php create mode 100644 tests/Wallabag/CoreBundle/fixtures/unnamed.png diff --git a/src/Wallabag/CoreBundle/Event/Subscriber/DownloadImagesSubscriber.php b/src/Wallabag/CoreBundle/Event/Subscriber/DownloadImagesSubscriber.php new file mode 100644 index 00000000..654edf31 --- /dev/null +++ b/src/Wallabag/CoreBundle/Event/Subscriber/DownloadImagesSubscriber.php @@ -0,0 +1,129 @@ +downloadImages = $downloadImages; + $this->configClass = $configClass; + $this->logger = $logger; + } + + public function getSubscribedEvents() + { + return array( + 'prePersist', + 'preUpdate', + ); + } + + /** + * In case of an entry has been updated. + * We won't update the content field if it wasn't updated. + * + * @param LifecycleEventArgs $args + */ + public function preUpdate(LifecycleEventArgs $args) + { + $entity = $args->getEntity(); + + if (!$entity instanceof Entry) { + return; + } + + $em = $args->getEntityManager(); + + // field content has been updated + if ($args->hasChangedField('content')) { + $html = $this->downloadImages($em, $entity); + + if (null !== $html) { + $args->setNewValue('content', $html); + } + } + + // field preview picture has been updated + if ($args->hasChangedField('previewPicture')) { + $previewPicture = $this->downloadPreviewImage($em, $entity); + + if (null !== $previewPicture) { + $entity->setPreviewPicture($previewPicture); + } + } + } + + /** + * When a new entry is saved. + * + * @param LifecycleEventArgs $args + */ + public function prePersist(LifecycleEventArgs $args) + { + $entity = $args->getEntity(); + + if (!$entity instanceof Entry) { + return; + } + + $config = new $this->configClass(); + $config->setEntityManager($args->getEntityManager()); + + // update all images inside the html + $html = $this->downloadImages($config, $entity); + if (null !== $html) { + $entity->setContent($html); + } + + // update preview picture + $previewPicture = $this->downloadPreviewImage($config, $entity); + if (null !== $previewPicture) { + $entity->setPreviewPicture($previewPicture); + } + } + + public function downloadImages(Config $config, Entry $entry) + { + // if ($config->get('download_images_with_rabbitmq')) { + + // } else if ($config->get('download_images_with_redis')) { + + // } + + return $this->downloadImages->processHtml( + $entry->getContent(), + $entry->getUrl() + ); + } + + public function downloadPreviewImage(Config $config, Entry $entry) + { + // if ($config->get('download_images_with_rabbitmq')) { + + // } else if ($config->get('download_images_with_redis')) { + + // } + + return $this->downloadImages->processSingleImage( + $entry->getPreviewPicture(), + $entry->getUrl() + ); + } +} diff --git a/src/Wallabag/CoreBundle/Helper/ContentProxy.php b/src/Wallabag/CoreBundle/Helper/ContentProxy.php index 219b90d3..d90d3dc8 100644 --- a/src/Wallabag/CoreBundle/Helper/ContentProxy.php +++ b/src/Wallabag/CoreBundle/Helper/ContentProxy.php @@ -75,12 +75,6 @@ class ContentProxy $entry->setDomainName($domainName); } - if (true) { - $this->logger->log('debug', 'Starting to download images'); - $downloadImages = new DownloadImages($html, $url, $this->logger); - $html = $downloadImages->process(); - } - $entry->setContent($html); if (isset($content['open_graph']['og_image'])) { diff --git a/src/Wallabag/CoreBundle/Helper/DownloadImages.php b/src/Wallabag/CoreBundle/Helper/DownloadImages.php index e23e0c55..426cbe48 100644 --- a/src/Wallabag/CoreBundle/Helper/DownloadImages.php +++ b/src/Wallabag/CoreBundle/Helper/DownloadImages.php @@ -2,193 +2,197 @@ namespace Wallabag\CoreBundle\Helper; -use Psr\Log\LoggerInterface as Logger; +use Psr\Log\LoggerInterface; use Symfony\Component\DomCrawler\Crawler; - -define('REGENERATE_PICTURES_QUALITY', 75); -define('HTTP_PORT', 80); -define('SSL_PORT', 443); -define('BASE_URL', ''); +use GuzzleHttp\Client; +use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeExtensionGuesser; class DownloadImages { - private $folder; - private $url; - private $html; - private $fileName; + const REGENERATE_PICTURES_QUALITY = 80; + + private $client; + private $baseFolder; private $logger; + private $mimeGuesser; - public function __construct($html, $url, Logger $logger) + public function __construct(Client $client, $baseFolder, LoggerInterface $logger) { - $this->html = $html; - $this->url = $url; - $this->setFolder(); + $this->client = $client; + $this->baseFolder = $baseFolder; $this->logger = $logger; + $this->mimeGuesser = new MimeTypeExtensionGuesser(); + + $this->setFolder(); } - public function setFolder($folder = 'assets/images') + /** + * Setup base folder where all images are going to be saved. + */ + private function setFolder() { // if folder doesn't exist, attempt to create one and store the folder name in property $folder - if (!file_exists($folder)) { - mkdir($folder); + if (!file_exists($this->baseFolder)) { + mkdir($this->baseFolder, 0777, true); } - $this->folder = $folder; } - public function process() + /** + * Process the html and extract image from it, save them to local and return the updated html. + * + * @param string $html + * @param string $url Used as a base path for relative image and folder + * + * @return string + */ + public function processHtml($html, $url) { - //instantiate the symfony DomCrawler Component - $crawler = new Crawler($this->html); - // create an array of all scrapped image links - $this->logger->log('debug', 'Finding images inside document'); + $crawler = new Crawler($html); $result = $crawler ->filterXpath('//img') ->extract(array('src')); + $relativePath = $this->getRelativePath($url); + // download and save the image to the folder foreach ($result as $image) { - $file = file_get_contents($image); - - // Checks - $absolute_path = self::getAbsoluteLink($image, $this->url); - $filename = basename(parse_url($absolute_path, PHP_URL_PATH)); - $fullpath = $this->folder.'/'.$filename; - self::checks($file, $fullpath, $absolute_path); - $this->html = str_replace($image, self::getPocheUrl().'/'.$fullpath, $this->html); + $imagePath = $this->processSingleImage($image, $url, $relativePath); + + if (false === $imagePath) { + continue; + } + + $html = str_replace($image, $imagePath, $html); } - return $this->html; + return $html; } - private function checks($rawdata, $fullpath, $absolute_path) + /** + * Process a single image: + * - retrieve it + * - re-saved it (for security reason) + * - return the new local path. + * + * @param string $imagePath Path to the image to retrieve + * @param string $url Url from where the image were found + * @param string $relativePath Relative local path to saved the image + * + * @return string Relative url to access the image from the web + */ + public function processSingleImage($imagePath, $url, $relativePath = null) { - $fullpath = urldecode($fullpath); - - if (file_exists($fullpath)) { - unlink($fullpath); + if (null == $relativePath) { + $relativePath = $this->getRelativePath($url); } - // check extension - $this->logger->log('debug', 'Checking extension'); + $folderPath = $this->baseFolder.'/'.$relativePath; - $file_ext = strrchr($fullpath, '.'); - $whitelist = array('.jpg', '.jpeg', '.gif', '.png'); - if (!(in_array($file_ext, $whitelist))) { - $this->logger->log('debug', 'processed image with not allowed extension. Skipping '.$fullpath); + // build image path + $absolutePath = $this->getAbsoluteLink($url, $imagePath); + if (false === $absolutePath) { + $this->logger->log('debug', 'Can not determine the absolute path for that image, skipping.'); return false; } - // check headers - $this->logger->log('debug', 'Checking headers'); - $imageinfo = getimagesize($absolute_path); - if ($imageinfo['mime'] != 'image/gif' && $imageinfo['mime'] != 'image/jpeg' && $imageinfo['mime'] != 'image/jpg' && $imageinfo['mime'] != 'image/png') { - $this->logger->log('debug', 'processed image with bad header. Skipping '.$fullpath); + $res = $this->client->get( + $absolutePath, + ['exceptions' => false] + ); + + $ext = $this->mimeGuesser->guess($res->getHeader('content-type')); + $this->logger->log('debug', 'Checking extension', ['ext' => $ext, 'header' => $res->getHeader('content-type')]); + if (!in_array($ext, ['jpeg', 'jpg', 'gif', 'png'])) { + $this->logger->log('debug', 'Processed image with not allowed extension. Skipping '.$imagePath); return false; } + $hashImage = hash('crc32', $absolutePath); + $localPath = $folderPath.'/'.$hashImage.'.'.$ext; + + try { + $im = imagecreatefromstring($res->getBody()); + } catch (\Exception $e) { + $im = false; + } - // regenerate image - $this->logger->log('debug', 'regenerating image'); - $im = imagecreatefromstring($rawdata); if ($im === false) { - $this->logger->log('error', 'error while regenerating image '.$fullpath); + $this->logger->log('error', 'Error while regenerating image', ['path' => $localPath]); return false; } - switch ($imageinfo['mime']) { - case 'image/gif': - $result = imagegif($im, $fullpath); + switch ($ext) { + case 'gif': + $result = imagegif($im, $localPath); $this->logger->log('debug', 'Re-creating gif'); break; - case 'image/jpeg': - case 'image/jpg': - $result = imagejpeg($im, $fullpath, REGENERATE_PICTURES_QUALITY); + case 'jpeg': + case 'jpg': + $result = imagejpeg($im, $localPath, self::REGENERATE_PICTURES_QUALITY); $this->logger->log('debug', 'Re-creating jpg'); break; - case 'image/png': + case 'png': + $result = imagepng($im, $localPath, ceil(self::REGENERATE_PICTURES_QUALITY / 100 * 9)); $this->logger->log('debug', 'Re-creating png'); - $result = imagepng($im, $fullpath, ceil(REGENERATE_PICTURES_QUALITY / 100 * 9)); - break; } + imagedestroy($im); - return $result; + return '/assets/images/'.$relativePath.'/'.$hashImage.'.'.$ext; } - private static function getAbsoluteLink($relativeLink, $url) + /** + * Generate the folder where we are going to save images based on the entry url. + * + * @param string $url + * + * @return string + */ + private function getRelativePath($url) { - /* return if already absolute URL */ - if (parse_url($relativeLink, PHP_URL_SCHEME) != '') { - return $relativeLink; - } + $hashUrl = hash('crc32', $url); + $relativePath = $hashUrl[0].'/'.$hashUrl[1].'/'.$hashUrl; + $folderPath = $this->baseFolder.'/'.$relativePath; - /* queries and anchors */ - if ($relativeLink[0] == '#' || $relativeLink[0] == '?') { - return $url.$relativeLink; + if (!file_exists($folderPath)) { + mkdir($folderPath, 0777, true); } - /* parse base URL and convert to local variables: - $scheme, $host, $path */ - extract(parse_url($url)); + $this->logger->log('debug', 'Folder used for that url', ['folder' => $folderPath, 'url' => $url]); - /* remove non-directory element from path */ - $path = preg_replace('#/[^/]*$#', '', $path); + return $relativePath; + } - /* destroy path if relative url points to root */ - if ($relativeLink[0] == '/') { - $path = ''; + /** + * Make an $url absolute based on the $base. + * + * @see Graby->makeAbsoluteStr + * + * @param string $base Base url + * @param string $url Url to make it absolute + * + * @return false|string + */ + private function getAbsoluteLink($base, $url) + { + if (preg_match('!^https?://!i', $url)) { + // already absolute + return $url; } - /* dirty absolute URL */ - $abs = $host.$path.'/'.$relativeLink; + $base = new \SimplePie_IRI($base); - /* replace '//' or '/./' or '/foo/../' with '/' */ - $re = array('#(/\.?/)#', '#/(?!\.\.)[^/]+/\.\./#'); - for ($n = 1; $n > 0; $abs = preg_replace($re, '/', $abs, -1, $n)) { + // remove '//' in URL path (causes URLs not to resolve properly) + if (isset($base->ipath)) { + $base->ipath = preg_replace('!//+!', '/', $base->ipath); } - /* absolute URL is ready! */ - return $scheme.'://'.$abs; - } - - public static function getPocheUrl() - { - $baseUrl = ''; - $https = (!empty($_SERVER['HTTPS']) - && (strtolower($_SERVER['HTTPS']) == 'on')) - || (isset($_SERVER['SERVER_PORT']) - && $_SERVER['SERVER_PORT'] == '443') // HTTPS detection. - || (isset($_SERVER['SERVER_PORT']) //Custom HTTPS port detection - && $_SERVER['SERVER_PORT'] == SSL_PORT) - || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) - && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https'); - $serverport = (!isset($_SERVER['SERVER_PORT']) - || $_SERVER['SERVER_PORT'] == '80' - || $_SERVER['SERVER_PORT'] == HTTP_PORT - || ($https && $_SERVER['SERVER_PORT'] == '443') - || ($https && $_SERVER['SERVER_PORT'] == SSL_PORT) //Custom HTTPS port detection - ? '' : ':'.$_SERVER['SERVER_PORT']); - - if (isset($_SERVER['HTTP_X_FORWARDED_PORT'])) { - $serverport = ':'.$_SERVER['HTTP_X_FORWARDED_PORT']; - } - // $scriptname = str_replace('/index.php', '/', $_SERVER["SCRIPT_NAME"]); - // if (!isset($_SERVER["HTTP_HOST"])) { - // return $scriptname; - // } - $host = (isset($_SERVER['HTTP_X_FORWARDED_HOST']) ? $_SERVER['HTTP_X_FORWARDED_HOST'] : (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : $_SERVER['SERVER_NAME'])); - if (strpos($host, ':') !== false) { - $serverport = ''; - } - // check if BASE_URL is configured - if (BASE_URL) { - $baseUrl = BASE_URL; - } else { - $baseUrl = 'http'.($https ? 's' : '').'://'.$host.$serverport; + if ($absolute = \SimplePie_IRI::absolutize($base, $url)) { + return $absolute->get_uri(); } - return $baseUrl; + return false; } } diff --git a/src/Wallabag/CoreBundle/Resources/config/services.yml b/src/Wallabag/CoreBundle/Resources/config/services.yml index 4b7751fe..1fb81a46 100644 --- a/src/Wallabag/CoreBundle/Resources/config/services.yml +++ b/src/Wallabag/CoreBundle/Resources/config/services.yml @@ -136,3 +136,22 @@ services: - "@doctrine" tags: - { name: doctrine.event_subscriber } + + wallabag_core.subscriber.download_images: + class: Wallabag\CoreBundle\Event\Subscriber\DownloadImagesSubscriber + arguments: + - "@wallabag_core.entry.download_images" + - "%craue_config.config.class%" + - "@logger" + tags: + - { name: doctrine.event_subscriber } + + wallabag_core.entry.download_images: + class: Wallabag\CoreBundle\Helper\DownloadImages + arguments: + - "@wallabag_core.entry.download_images.client" + - "%kernel.root_dir%/../web/assets/images" + - "@logger" + + wallabag_core.entry.download_images.client: + class: GuzzleHttp\Client diff --git a/tests/Wallabag/CoreBundle/Helper/DownloadImagesTest.php b/tests/Wallabag/CoreBundle/Helper/DownloadImagesTest.php new file mode 100644 index 00000000..0273693e --- /dev/null +++ b/tests/Wallabag/CoreBundle/Helper/DownloadImagesTest.php @@ -0,0 +1,123 @@ + 'image/png'], Stream::factory(file_get_contents(__DIR__.'/../fixtures/unnamed.png'))), + ]); + + $client->getEmitter()->attach($mock); + + $logHandler = new TestHandler(); + $logger = new Logger('test', array($logHandler)); + + $download = new DownloadImages($client, sys_get_temp_dir().'/wallabag_test', $logger); + $res = $download->processHtml('
', 'http://imgur.com/gallery/WxtWY'); + + $this->assertContains('/assets/images/4/2/4258f71e/c638b4c2.png', $res); + } + + public function testProcessHtmlWithBadImage() + { + $client = new Client(); + + $mock = new Mock([ + new Response(200, ['content-type' => 'application/json'], Stream::factory('')), + ]); + + $client->getEmitter()->attach($mock); + + $logHandler = new TestHandler(); + $logger = new Logger('test', array($logHandler)); + + $download = new DownloadImages($client, sys_get_temp_dir().'/wallabag_test', $logger); + $res = $download->processHtml('
', 'http://imgur.com/gallery/WxtWY'); + + $this->assertContains('http://i.imgur.com/T9qgcHc.jpg', $res, 'Image were not replace because of content-type'); + } + + public function singleImage() + { + return [ + ['image/pjpeg', 'jpeg'], + ['image/jpeg', 'jpeg'], + ['image/png', 'png'], + ['image/gif', 'gif'], + ]; + } + + /** + * @dataProvider singleImage + */ + public function testProcessSingleImage($header, $extension) + { + $client = new Client(); + + $mock = new Mock([ + new Response(200, ['content-type' => $header], Stream::factory(file_get_contents(__DIR__.'/../fixtures/unnamed.png'))), + ]); + + $client->getEmitter()->attach($mock); + + $logHandler = new TestHandler(); + $logger = new Logger('test', array($logHandler)); + + $download = new DownloadImages($client, sys_get_temp_dir().'/wallabag_test', $logger); + $res = $download->processSingleImage('T9qgcHc.jpg', 'http://imgur.com/gallery/WxtWY'); + + $this->assertContains('/assets/images/4/2/4258f71e/ebe60399.'.$extension, $res); + } + + public function testProcessSingleImageWithBadImage() + { + $client = new Client(); + + $mock = new Mock([ + new Response(200, ['content-type' => 'image/png'], Stream::factory('')), + ]); + + $client->getEmitter()->attach($mock); + + $logHandler = new TestHandler(); + $logger = new Logger('test', array($logHandler)); + + $download = new DownloadImages($client, sys_get_temp_dir().'/wallabag_test', $logger); + $res = $download->processSingleImage('http://i.imgur.com/T9qgcHc.jpg', 'http://imgur.com/gallery/WxtWY'); + + $this->assertFalse($res, 'Image can not be loaded, so it will not be replaced'); + } + + public function testProcessSingleImageFailAbsolute() + { + $client = new Client(); + + $mock = new Mock([ + new Response(200, ['content-type' => 'image/png'], Stream::factory(file_get_contents(__DIR__.'/../fixtures/unnamed.png'))), + ]); + + $client->getEmitter()->attach($mock); + + $logHandler = new TestHandler(); + $logger = new Logger('test', array($logHandler)); + + $download = new DownloadImages($client, sys_get_temp_dir().'/wallabag_test', $logger); + $res = $download->processSingleImage('/i.imgur.com/T9qgcHc.jpg', 'imgur.com/gallery/WxtWY'); + + $this->assertFalse($res, 'Absolute image can not be determined, so it will not be replaced'); + } +} diff --git a/tests/Wallabag/CoreBundle/fixtures/unnamed.png b/tests/Wallabag/CoreBundle/fixtures/unnamed.png new file mode 100644 index 00000000..e6dd9caa Binary files /dev/null and b/tests/Wallabag/CoreBundle/fixtures/unnamed.png differ -- cgit v1.2.3 From 48656e0eaac006a80f21e9aec8900747fe76283a Mon Sep 17 00:00:00 2001 From: Jeremy Benoist Date: Sun, 30 Oct 2016 11:27:09 +0100 Subject: Fixing tests --- .../Event/Subscriber/DownloadImagesSubscriber.php | 31 ++++-- src/Wallabag/CoreBundle/Helper/ContentProxy.php | 3 +- src/Wallabag/CoreBundle/Helper/DownloadImages.php | 21 ++-- .../Event/Listener/LocaleListenerTest.php | 86 ++++++++++++++++ .../Event/Listener/UserLocaleListenerTest.php | 58 +++++++++++ .../Event/Subscriber/TablePrefixSubscriberTest.php | 114 +++++++++++++++++++++ .../EventListener/LocaleListenerTest.php | 86 ---------------- .../EventListener/UserLocaleListenerTest.php | 58 ----------- .../CoreBundle/Helper/DownloadImagesTest.php | 19 ++++ .../Subscriber/TablePrefixSubscriberTest.php | 114 --------------------- 10 files changed, 315 insertions(+), 275 deletions(-) create mode 100644 tests/Wallabag/CoreBundle/Event/Listener/LocaleListenerTest.php create mode 100644 tests/Wallabag/CoreBundle/Event/Listener/UserLocaleListenerTest.php create mode 100644 tests/Wallabag/CoreBundle/Event/Subscriber/TablePrefixSubscriberTest.php delete mode 100644 tests/Wallabag/CoreBundle/EventListener/LocaleListenerTest.php delete mode 100644 tests/Wallabag/CoreBundle/EventListener/UserLocaleListenerTest.php delete mode 100644 tests/Wallabag/CoreBundle/Subscriber/TablePrefixSubscriberTest.php diff --git a/src/Wallabag/CoreBundle/Event/Subscriber/DownloadImagesSubscriber.php b/src/Wallabag/CoreBundle/Event/Subscriber/DownloadImagesSubscriber.php index 654edf31..09f8e911 100644 --- a/src/Wallabag/CoreBundle/Event/Subscriber/DownloadImagesSubscriber.php +++ b/src/Wallabag/CoreBundle/Event/Subscriber/DownloadImagesSubscriber.php @@ -49,22 +49,23 @@ class DownloadImagesSubscriber implements EventSubscriber return; } - $em = $args->getEntityManager(); + $config = new $this->configClass(); + $config->setEntityManager($args->getEntityManager()); // field content has been updated if ($args->hasChangedField('content')) { - $html = $this->downloadImages($em, $entity); + $html = $this->downloadImages($config, $entity); - if (null !== $html) { + if (false !== $html) { $args->setNewValue('content', $html); } } // field preview picture has been updated if ($args->hasChangedField('previewPicture')) { - $previewPicture = $this->downloadPreviewImage($em, $entity); + $previewPicture = $this->downloadPreviewImage($config, $entity); - if (null !== $previewPicture) { + if (false !== $previewPicture) { $entity->setPreviewPicture($previewPicture); } } @@ -88,17 +89,25 @@ class DownloadImagesSubscriber implements EventSubscriber // update all images inside the html $html = $this->downloadImages($config, $entity); - if (null !== $html) { + if (false !== $html) { $entity->setContent($html); } // update preview picture $previewPicture = $this->downloadPreviewImage($config, $entity); - if (null !== $previewPicture) { + if (false !== $previewPicture) { $entity->setPreviewPicture($previewPicture); } } + /** + * Download all images from the html. + * + * @param Config $config + * @param Entry $entry + * + * @return string|false False in case of async + */ public function downloadImages(Config $config, Entry $entry) { // if ($config->get('download_images_with_rabbitmq')) { @@ -113,6 +122,14 @@ class DownloadImagesSubscriber implements EventSubscriber ); } + /** + * Download the preview picture. + * + * @param Config $config + * @param Entry $entry + * + * @return string|false False in case of async + */ public function downloadPreviewImage(Config $config, Entry $entry) { // if ($config->get('download_images_with_rabbitmq')) { diff --git a/src/Wallabag/CoreBundle/Helper/ContentProxy.php b/src/Wallabag/CoreBundle/Helper/ContentProxy.php index d90d3dc8..1986ab33 100644 --- a/src/Wallabag/CoreBundle/Helper/ContentProxy.php +++ b/src/Wallabag/CoreBundle/Helper/ContentProxy.php @@ -65,6 +65,7 @@ class ContentProxy $entry->setUrl($content['url'] ?: $url); $entry->setTitle($title); + $entry->setContent($html); $entry->setLanguage($content['language']); $entry->setMimetype($content['content_type']); @@ -75,8 +76,6 @@ class ContentProxy $entry->setDomainName($domainName); } - $entry->setContent($html); - if (isset($content['open_graph']['og_image'])) { $entry->setPreviewPicture($content['open_graph']['og_image']); } diff --git a/src/Wallabag/CoreBundle/Helper/DownloadImages.php b/src/Wallabag/CoreBundle/Helper/DownloadImages.php index 426cbe48..004bb277 100644 --- a/src/Wallabag/CoreBundle/Helper/DownloadImages.php +++ b/src/Wallabag/CoreBundle/Helper/DownloadImages.php @@ -91,20 +91,23 @@ class DownloadImages // build image path $absolutePath = $this->getAbsoluteLink($url, $imagePath); if (false === $absolutePath) { - $this->logger->log('debug', 'Can not determine the absolute path for that image, skipping.'); + $this->logger->log('error', 'Can not determine the absolute path for that image, skipping.'); return false; } - $res = $this->client->get( - $absolutePath, - ['exceptions' => false] - ); + try { + $res = $this->client->get($absolutePath); + } catch (\Exception $e) { + $this->logger->log('error', 'Can not retrieve image, skipping.', ['exception' => $e]); + + return false; + } $ext = $this->mimeGuesser->guess($res->getHeader('content-type')); $this->logger->log('debug', 'Checking extension', ['ext' => $ext, 'header' => $res->getHeader('content-type')]); - if (!in_array($ext, ['jpeg', 'jpg', 'gif', 'png'])) { - $this->logger->log('debug', 'Processed image with not allowed extension. Skipping '.$imagePath); + if (!in_array($ext, ['jpeg', 'jpg', 'gif', 'png'], true)) { + $this->logger->log('error', 'Processed image with not allowed extension. Skipping '.$imagePath); return false; } @@ -117,7 +120,7 @@ class DownloadImages $im = false; } - if ($im === false) { + if (false === $im) { $this->logger->log('error', 'Error while regenerating image', ['path' => $localPath]); return false; @@ -193,6 +196,8 @@ class DownloadImages return $absolute->get_uri(); } + $this->logger->log('error', 'Can not make an absolute link', ['base' => $base, 'url' => $url]); + return false; } } diff --git a/tests/Wallabag/CoreBundle/Event/Listener/LocaleListenerTest.php b/tests/Wallabag/CoreBundle/Event/Listener/LocaleListenerTest.php new file mode 100644 index 00000000..84a54d3a --- /dev/null +++ b/tests/Wallabag/CoreBundle/Event/Listener/LocaleListenerTest.php @@ -0,0 +1,86 @@ +getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface') + ->disableOriginalConstructor() + ->getMock(); + + return new GetResponseEvent($kernel, $request, HttpKernelInterface::MASTER_REQUEST); + } + + public function testWithoutSession() + { + $request = Request::create('/'); + + $listener = new LocaleListener('fr'); + $event = $this->getEvent($request); + + $listener->onKernelRequest($event); + $this->assertEquals('en', $request->getLocale()); + } + + public function testWithPreviousSession() + { + $request = Request::create('/'); + // generate a previous session + $request->cookies->set('MOCKSESSID', 'foo'); + $request->setSession(new Session(new MockArraySessionStorage())); + + $listener = new LocaleListener('fr'); + $event = $this->getEvent($request); + + $listener->onKernelRequest($event); + $this->assertEquals('fr', $request->getLocale()); + } + + public function testLocaleFromRequestAttribute() + { + $request = Request::create('/'); + // generate a previous session + $request->cookies->set('MOCKSESSID', 'foo'); + $request->setSession(new Session(new MockArraySessionStorage())); + $request->attributes->set('_locale', 'es'); + + $listener = new LocaleListener('fr'); + $event = $this->getEvent($request); + + $listener->onKernelRequest($event); + $this->assertEquals('en', $request->getLocale()); + $this->assertEquals('es', $request->getSession()->get('_locale')); + } + + public function testSubscribedEvents() + { + $request = Request::create('/'); + // generate a previous session + $request->cookies->set('MOCKSESSID', 'foo'); + $request->setSession(new Session(new MockArraySessionStorage())); + + $listener = new LocaleListener('fr'); + $event = $this->getEvent($request); + + $dispatcher = new EventDispatcher(); + $dispatcher->addSubscriber($listener); + + $dispatcher->dispatch( + KernelEvents::REQUEST, + $event + ); + + $this->assertEquals('fr', $request->getLocale()); + } +} diff --git a/tests/Wallabag/CoreBundle/Event/Listener/UserLocaleListenerTest.php b/tests/Wallabag/CoreBundle/Event/Listener/UserLocaleListenerTest.php new file mode 100644 index 00000000..45aecc63 --- /dev/null +++ b/tests/Wallabag/CoreBundle/Event/Listener/UserLocaleListenerTest.php @@ -0,0 +1,58 @@ +setEnabled(true); + + $config = new Config($user); + $config->setLanguage('fr'); + + $user->setConfig($config); + + $userToken = new UsernamePasswordToken($user, '', 'test'); + $request = Request::create('/'); + $event = new InteractiveLoginEvent($request, $userToken); + + $listener->onInteractiveLogin($event); + + $this->assertEquals('fr', $session->get('_locale')); + } + + public function testWithoutLanguage() + { + $session = new Session(new MockArraySessionStorage()); + $listener = new UserLocaleListener($session); + + $user = new User(); + $user->setEnabled(true); + + $config = new Config($user); + + $user->setConfig($config); + + $userToken = new UsernamePasswordToken($user, '', 'test'); + $request = Request::create('/'); + $event = new InteractiveLoginEvent($request, $userToken); + + $listener->onInteractiveLogin($event); + + $this->assertEquals('', $session->get('_locale')); + } +} diff --git a/tests/Wallabag/CoreBundle/Event/Subscriber/TablePrefixSubscriberTest.php b/tests/Wallabag/CoreBundle/Event/Subscriber/TablePrefixSubscriberTest.php new file mode 100644 index 00000000..b8cd0fad --- /dev/null +++ b/tests/Wallabag/CoreBundle/Event/Subscriber/TablePrefixSubscriberTest.php @@ -0,0 +1,114 @@ +getMockBuilder('Doctrine\ORM\EntityManager') + ->disableOriginalConstructor() + ->getMock(); + + $subscriber = new TablePrefixSubscriber($prefix); + + $metaClass = new ClassMetadata($entityName); + $metaClass->setPrimaryTable(['name' => $tableName]); + + $metaDataEvent = new LoadClassMetadataEventArgs($metaClass, $em); + + $this->assertEquals($tableNameExpected, $metaDataEvent->getClassMetadata()->getTableName()); + + $subscriber->loadClassMetadata($metaDataEvent); + + $this->assertEquals($finalTableName, $metaDataEvent->getClassMetadata()->getTableName()); + $this->assertEquals($finalTableNameQuoted, $metaDataEvent->getClassMetadata()->getQuotedTableName($platform)); + } + + /** + * @dataProvider dataForPrefix + */ + public function testSubscribedEvents($prefix, $entityName, $tableName, $tableNameExpected, $finalTableName, $finalTableNameQuoted, $platform) + { + $em = $this->getMockBuilder('Doctrine\ORM\EntityManager') + ->disableOriginalConstructor() + ->getMock(); + + $metaClass = new ClassMetadata($entityName); + $metaClass->setPrimaryTable(['name' => $tableName]); + + $metaDataEvent = new LoadClassMetadataEventArgs($metaClass, $em); + + $subscriber = new TablePrefixSubscriber($prefix); + + $evm = new EventManager(); + $evm->addEventSubscriber($subscriber); + + $evm->dispatchEvent('loadClassMetadata', $metaDataEvent); + + $this->assertEquals($finalTableName, $metaDataEvent->getClassMetadata()->getTableName()); + $this->assertEquals($finalTableNameQuoted, $metaDataEvent->getClassMetadata()->getQuotedTableName($platform)); + } + + public function testPrefixManyToMany() + { + $em = $this->getMockBuilder('Doctrine\ORM\EntityManager') + ->disableOriginalConstructor() + ->getMock(); + + $subscriber = new TablePrefixSubscriber('yo_'); + + $metaClass = new ClassMetadata('Wallabag\UserBundle\Entity\Entry'); + $metaClass->setPrimaryTable(['name' => 'entry']); + $metaClass->mapManyToMany([ + 'fieldName' => 'tags', + 'joinTable' => ['name' => null, 'schema' => null], + 'targetEntity' => 'Tag', + 'mappedBy' => null, + 'inversedBy' => 'entries', + 'cascade' => ['persist'], + 'indexBy' => null, + 'orphanRemoval' => false, + 'fetch' => 2, + ]); + + $metaDataEvent = new LoadClassMetadataEventArgs($metaClass, $em); + + $this->assertEquals('entry', $metaDataEvent->getClassMetadata()->getTableName()); + + $subscriber->loadClassMetadata($metaDataEvent); + + $this->assertEquals('yo_entry', $metaDataEvent->getClassMetadata()->getTableName()); + $this->assertEquals('yo_entry_tag', $metaDataEvent->getClassMetadata()->associationMappings['tags']['joinTable']['name']); + $this->assertEquals('yo_entry', $metaDataEvent->getClassMetadata()->getQuotedTableName(new \Doctrine\DBAL\Platforms\MySqlPlatform())); + } +} diff --git a/tests/Wallabag/CoreBundle/EventListener/LocaleListenerTest.php b/tests/Wallabag/CoreBundle/EventListener/LocaleListenerTest.php deleted file mode 100644 index 078bb69a..00000000 --- a/tests/Wallabag/CoreBundle/EventListener/LocaleListenerTest.php +++ /dev/null @@ -1,86 +0,0 @@ -getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface') - ->disableOriginalConstructor() - ->getMock(); - - return new GetResponseEvent($kernel, $request, HttpKernelInterface::MASTER_REQUEST); - } - - public function testWithoutSession() - { - $request = Request::create('/'); - - $listener = new LocaleListener('fr'); - $event = $this->getEvent($request); - - $listener->onKernelRequest($event); - $this->assertEquals('en', $request->getLocale()); - } - - public function testWithPreviousSession() - { - $request = Request::create('/'); - // generate a previous session - $request->cookies->set('MOCKSESSID', 'foo'); - $request->setSession(new Session(new MockArraySessionStorage())); - - $listener = new LocaleListener('fr'); - $event = $this->getEvent($request); - - $listener->onKernelRequest($event); - $this->assertEquals('fr', $request->getLocale()); - } - - public function testLocaleFromRequestAttribute() - { - $request = Request::create('/'); - // generate a previous session - $request->cookies->set('MOCKSESSID', 'foo'); - $request->setSession(new Session(new MockArraySessionStorage())); - $request->attributes->set('_locale', 'es'); - - $listener = new LocaleListener('fr'); - $event = $this->getEvent($request); - - $listener->onKernelRequest($event); - $this->assertEquals('en', $request->getLocale()); - $this->assertEquals('es', $request->getSession()->get('_locale')); - } - - public function testSubscribedEvents() - { - $request = Request::create('/'); - // generate a previous session - $request->cookies->set('MOCKSESSID', 'foo'); - $request->setSession(new Session(new MockArraySessionStorage())); - - $listener = new LocaleListener('fr'); - $event = $this->getEvent($request); - - $dispatcher = new EventDispatcher(); - $dispatcher->addSubscriber($listener); - - $dispatcher->dispatch( - KernelEvents::REQUEST, - $event - ); - - $this->assertEquals('fr', $request->getLocale()); - } -} diff --git a/tests/Wallabag/CoreBundle/EventListener/UserLocaleListenerTest.php b/tests/Wallabag/CoreBundle/EventListener/UserLocaleListenerTest.php deleted file mode 100644 index e9ac7c1d..00000000 --- a/tests/Wallabag/CoreBundle/EventListener/UserLocaleListenerTest.php +++ /dev/null @@ -1,58 +0,0 @@ -setEnabled(true); - - $config = new Config($user); - $config->setLanguage('fr'); - - $user->setConfig($config); - - $userToken = new UsernamePasswordToken($user, '', 'test'); - $request = Request::create('/'); - $event = new InteractiveLoginEvent($request, $userToken); - - $listener->onInteractiveLogin($event); - - $this->assertEquals('fr', $session->get('_locale')); - } - - public function testWithoutLanguage() - { - $session = new Session(new MockArraySessionStorage()); - $listener = new UserLocaleListener($session); - - $user = new User(); - $user->setEnabled(true); - - $config = new Config($user); - - $user->setConfig($config); - - $userToken = new UsernamePasswordToken($user, '', 'test'); - $request = Request::create('/'); - $event = new InteractiveLoginEvent($request, $userToken); - - $listener->onInteractiveLogin($event); - - $this->assertEquals('', $session->get('_locale')); - } -} diff --git a/tests/Wallabag/CoreBundle/Helper/DownloadImagesTest.php b/tests/Wallabag/CoreBundle/Helper/DownloadImagesTest.php index 0273693e..e000d681 100644 --- a/tests/Wallabag/CoreBundle/Helper/DownloadImagesTest.php +++ b/tests/Wallabag/CoreBundle/Helper/DownloadImagesTest.php @@ -83,6 +83,25 @@ class DownloadImagesTest extends \PHPUnit_Framework_TestCase $this->assertContains('/assets/images/4/2/4258f71e/ebe60399.'.$extension, $res); } + public function testProcessSingleImageWithBadUrl() + { + $client = new Client(); + + $mock = new Mock([ + new Response(404, []), + ]); + + $client->getEmitter()->attach($mock); + + $logHandler = new TestHandler(); + $logger = new Logger('test', array($logHandler)); + + $download = new DownloadImages($client, sys_get_temp_dir().'/wallabag_test', $logger); + $res = $download->processSingleImage('T9qgcHc.jpg', 'http://imgur.com/gallery/WxtWY'); + + $this->assertFalse($res, 'Image can not be found, so it will not be replaced'); + } + public function testProcessSingleImageWithBadImage() { $client = new Client(); diff --git a/tests/Wallabag/CoreBundle/Subscriber/TablePrefixSubscriberTest.php b/tests/Wallabag/CoreBundle/Subscriber/TablePrefixSubscriberTest.php deleted file mode 100644 index 4ae76703..00000000 --- a/tests/Wallabag/CoreBundle/Subscriber/TablePrefixSubscriberTest.php +++ /dev/null @@ -1,114 +0,0 @@ -getMockBuilder('Doctrine\ORM\EntityManager') - ->disableOriginalConstructor() - ->getMock(); - - $subscriber = new TablePrefixSubscriber($prefix); - - $metaClass = new ClassMetadata($entityName); - $metaClass->setPrimaryTable(['name' => $tableName]); - - $metaDataEvent = new LoadClassMetadataEventArgs($metaClass, $em); - - $this->assertEquals($tableNameExpected, $metaDataEvent->getClassMetadata()->getTableName()); - - $subscriber->loadClassMetadata($metaDataEvent); - - $this->assertEquals($finalTableName, $metaDataEvent->getClassMetadata()->getTableName()); - $this->assertEquals($finalTableNameQuoted, $metaDataEvent->getClassMetadata()->getQuotedTableName($platform)); - } - - /** - * @dataProvider dataForPrefix - */ - public function testSubscribedEvents($prefix, $entityName, $tableName, $tableNameExpected, $finalTableName, $finalTableNameQuoted, $platform) - { - $em = $this->getMockBuilder('Doctrine\ORM\EntityManager') - ->disableOriginalConstructor() - ->getMock(); - - $metaClass = new ClassMetadata($entityName); - $metaClass->setPrimaryTable(['name' => $tableName]); - - $metaDataEvent = new LoadClassMetadataEventArgs($metaClass, $em); - - $subscriber = new TablePrefixSubscriber($prefix); - - $evm = new EventManager(); - $evm->addEventSubscriber($subscriber); - - $evm->dispatchEvent('loadClassMetadata', $metaDataEvent); - - $this->assertEquals($finalTableName, $metaDataEvent->getClassMetadata()->getTableName()); - $this->assertEquals($finalTableNameQuoted, $metaDataEvent->getClassMetadata()->getQuotedTableName($platform)); - } - - public function testPrefixManyToMany() - { - $em = $this->getMockBuilder('Doctrine\ORM\EntityManager') - ->disableOriginalConstructor() - ->getMock(); - - $subscriber = new TablePrefixSubscriber('yo_'); - - $metaClass = new ClassMetadata('Wallabag\UserBundle\Entity\Entry'); - $metaClass->setPrimaryTable(['name' => 'entry']); - $metaClass->mapManyToMany([ - 'fieldName' => 'tags', - 'joinTable' => ['name' => null, 'schema' => null], - 'targetEntity' => 'Tag', - 'mappedBy' => null, - 'inversedBy' => 'entries', - 'cascade' => ['persist'], - 'indexBy' => null, - 'orphanRemoval' => false, - 'fetch' => 2, - ]); - - $metaDataEvent = new LoadClassMetadataEventArgs($metaClass, $em); - - $this->assertEquals('entry', $metaDataEvent->getClassMetadata()->getTableName()); - - $subscriber->loadClassMetadata($metaDataEvent); - - $this->assertEquals('yo_entry', $metaDataEvent->getClassMetadata()->getTableName()); - $this->assertEquals('yo_entry_tag', $metaDataEvent->getClassMetadata()->associationMappings['tags']['joinTable']['name']); - $this->assertEquals('yo_entry', $metaDataEvent->getClassMetadata()->getQuotedTableName(new \Doctrine\DBAL\Platforms\MySqlPlatform())); - } -} -- cgit v1.2.3 From 41ada277f066ea57947bce05bcda63962b7fea55 Mon Sep 17 00:00:00 2001 From: Jeremy Benoist Date: Sun, 30 Oct 2016 19:50:00 +0100 Subject: Add instance url to the downloaded images --- .../Event/Subscriber/DownloadImagesSubscriber.php | 4 ++++ src/Wallabag/CoreBundle/Helper/DownloadImages.php | 14 +++++++++++++- tests/Wallabag/CoreBundle/Helper/DownloadImagesTest.php | 4 +++- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/Wallabag/CoreBundle/Event/Subscriber/DownloadImagesSubscriber.php b/src/Wallabag/CoreBundle/Event/Subscriber/DownloadImagesSubscriber.php index 09f8e911..0792653e 100644 --- a/src/Wallabag/CoreBundle/Event/Subscriber/DownloadImagesSubscriber.php +++ b/src/Wallabag/CoreBundle/Event/Subscriber/DownloadImagesSubscriber.php @@ -110,6 +110,8 @@ class DownloadImagesSubscriber implements EventSubscriber */ public function downloadImages(Config $config, Entry $entry) { + $this->downloadImages->setWallabagUrl($config->get('wallabag_url')); + // if ($config->get('download_images_with_rabbitmq')) { // } else if ($config->get('download_images_with_redis')) { @@ -132,6 +134,8 @@ class DownloadImagesSubscriber implements EventSubscriber */ public function downloadPreviewImage(Config $config, Entry $entry) { + $this->downloadImages->setWallabagUrl($config->get('wallabag_url')); + // if ($config->get('download_images_with_rabbitmq')) { // } else if ($config->get('download_images_with_redis')) { diff --git a/src/Wallabag/CoreBundle/Helper/DownloadImages.php b/src/Wallabag/CoreBundle/Helper/DownloadImages.php index 004bb277..e7982c56 100644 --- a/src/Wallabag/CoreBundle/Helper/DownloadImages.php +++ b/src/Wallabag/CoreBundle/Helper/DownloadImages.php @@ -15,6 +15,7 @@ class DownloadImages private $baseFolder; private $logger; private $mimeGuesser; + private $wallabagUrl; public function __construct(Client $client, $baseFolder, LoggerInterface $logger) { @@ -26,6 +27,17 @@ class DownloadImages $this->setFolder(); } + /** + * Since we can't inject CraueConfig service because it'll generate a circular reference when injected in the subscriber + * we use a different way to inject the current wallabag url. + * + * @param string $url Usually from `$config->get('wallabag_url')` + */ + public function setWallabagUrl($url) + { + $this->wallabagUrl = rtrim($url, '/'); + } + /** * Setup base folder where all images are going to be saved. */ @@ -143,7 +155,7 @@ class DownloadImages imagedestroy($im); - return '/assets/images/'.$relativePath.'/'.$hashImage.'.'.$ext; + return $this->wallabagUrl.'/assets/images/'.$relativePath.'/'.$hashImage.'.'.$ext; } /** diff --git a/tests/Wallabag/CoreBundle/Helper/DownloadImagesTest.php b/tests/Wallabag/CoreBundle/Helper/DownloadImagesTest.php index e000d681..33d2e389 100644 --- a/tests/Wallabag/CoreBundle/Helper/DownloadImagesTest.php +++ b/tests/Wallabag/CoreBundle/Helper/DownloadImagesTest.php @@ -27,9 +27,11 @@ class DownloadImagesTest extends \PHPUnit_Framework_TestCase $logger = new Logger('test', array($logHandler)); $download = new DownloadImages($client, sys_get_temp_dir().'/wallabag_test', $logger); + $download->setWallabagUrl('http://wallabag.io/'); + $res = $download->processHtml('
', 'http://imgur.com/gallery/WxtWY'); - $this->assertContains('/assets/images/4/2/4258f71e/c638b4c2.png', $res); + $this->assertContains('http://wallabag.io/assets/images/4/2/4258f71e/c638b4c2.png', $res); } public function testProcessHtmlWithBadImage() -- cgit v1.2.3 From 309e13c11b54277626f18616c41f68ae9656a403 Mon Sep 17 00:00:00 2001 From: Jeremy Benoist Date: Sun, 30 Oct 2016 20:12:34 +0100 Subject: Move settings before Entry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Because we need wallabag_url to be defined when we’ll insert entries --- src/Wallabag/CoreBundle/DataFixtures/ORM/LoadSettingData.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadSettingData.php b/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadSettingData.php index a5e1be65..12f66c19 100644 --- a/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadSettingData.php +++ b/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadSettingData.php @@ -158,6 +158,6 @@ class LoadSettingData extends AbstractFixture implements OrderedFixtureInterface */ public function getOrder() { - return 50; + return 29; } } -- cgit v1.2.3 From d1495dd0a456f0e35a09fb68679ee51f8d17bfe4 Mon Sep 17 00:00:00 2001 From: Jeremy Benoist Date: Sun, 30 Oct 2016 21:30:45 +0100 Subject: Ability to enable/disable downloading images MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This will speed up the test suite because it won’t download everything when we add new entry… Add a custom test with downloading image enabled --- src/Wallabag/CoreBundle/Command/InstallCommand.php | 15 ++++++++ .../DataFixtures/ORM/LoadSettingData.php | 15 ++++++++ .../Event/Subscriber/DownloadImagesSubscriber.php | 8 +++++ .../CoreBundle/Controller/EntryControllerTest.php | 40 ++++++++++++++++++++++ 4 files changed, 78 insertions(+) diff --git a/src/Wallabag/CoreBundle/Command/InstallCommand.php b/src/Wallabag/CoreBundle/Command/InstallCommand.php index 277f8524..aedccfe4 100644 --- a/src/Wallabag/CoreBundle/Command/InstallCommand.php +++ b/src/Wallabag/CoreBundle/Command/InstallCommand.php @@ -398,6 +398,21 @@ class InstallCommand extends ContainerAwareCommand 'value' => 'wallabag', 'section' => 'misc', ], + [ + 'name' => 'download_images_enabled', + 'value' => '0', + 'section' => 'image', + ], + [ + 'name' => 'download_images_with_rabbitmq', + 'value' => '0', + 'section' => 'image', + ], + [ + 'name' => 'download_images_with_redis', + 'value' => '0', + 'section' => 'image', + ], ]; foreach ($settings as $setting) { diff --git a/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadSettingData.php b/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadSettingData.php index 12f66c19..70a7a4ac 100644 --- a/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadSettingData.php +++ b/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadSettingData.php @@ -140,6 +140,21 @@ class LoadSettingData extends AbstractFixture implements OrderedFixtureInterface 'value' => 'wallabag', 'section' => 'misc', ], + [ + 'name' => 'download_images_enabled', + 'value' => '0', + 'section' => 'image', + ], + [ + 'name' => 'download_images_with_rabbitmq', + 'value' => '0', + 'section' => 'image', + ], + [ + 'name' => 'download_images_with_redis', + 'value' => '0', + 'section' => 'image', + ], ]; foreach ($settings as $setting) { diff --git a/src/Wallabag/CoreBundle/Event/Subscriber/DownloadImagesSubscriber.php b/src/Wallabag/CoreBundle/Event/Subscriber/DownloadImagesSubscriber.php index 0792653e..3f2d460c 100644 --- a/src/Wallabag/CoreBundle/Event/Subscriber/DownloadImagesSubscriber.php +++ b/src/Wallabag/CoreBundle/Event/Subscriber/DownloadImagesSubscriber.php @@ -52,6 +52,10 @@ class DownloadImagesSubscriber implements EventSubscriber $config = new $this->configClass(); $config->setEntityManager($args->getEntityManager()); + if (!$config->get('download_images_enabled')) { + return; + } + // field content has been updated if ($args->hasChangedField('content')) { $html = $this->downloadImages($config, $entity); @@ -87,6 +91,10 @@ class DownloadImagesSubscriber implements EventSubscriber $config = new $this->configClass(); $config->setEntityManager($args->getEntityManager()); + if (!$config->get('download_images_enabled')) { + return; + } + // update all images inside the html $html = $this->downloadImages($config, $entity); if (false !== $html) { diff --git a/tests/Wallabag/CoreBundle/Controller/EntryControllerTest.php b/tests/Wallabag/CoreBundle/Controller/EntryControllerTest.php index 05113650..514e9d89 100644 --- a/tests/Wallabag/CoreBundle/Controller/EntryControllerTest.php +++ b/tests/Wallabag/CoreBundle/Controller/EntryControllerTest.php @@ -836,4 +836,44 @@ class EntryControllerTest extends WallabagCoreTestCase $client->request('GET', '/share/'.$content->getUuid()); $this->assertEquals(404, $client->getResponse()->getStatusCode()); } + + public function testNewEntryWithDownloadImagesEnabled() + { + $this->logInAs('admin'); + $client = $this->getClient(); + + $url = 'http://www.20minutes.fr/montpellier/1952003-20161030-video-car-tombe-panne-rugbymen-perpignan-improvisent-melee-route'; + $client->getContainer()->get('craue_config')->set('download_images_enabled', 1); + + $crawler = $client->request('GET', '/new'); + + $this->assertEquals(200, $client->getResponse()->getStatusCode()); + + $form = $crawler->filter('form[name=entry]')->form(); + + $data = [ + 'entry[url]' => $url, + ]; + + $client->submit($form, $data); + + $this->assertEquals(302, $client->getResponse()->getStatusCode()); + + $em = $client->getContainer() + ->get('doctrine.orm.entity_manager'); + + $entry = $em + ->getRepository('WallabagCoreBundle:Entry') + ->findByUrlAndUserId($url, $this->getLoggedInUserId()); + + $this->assertInstanceOf('Wallabag\CoreBundle\Entity\Entry', $entry); + $this->assertEquals($url, $entry->getUrl()); + $this->assertContains('Perpignan', $entry->getTitle()); + $this->assertContains('assets/images/8/e/8ec9229a/d9bc0fcd.jpeg', $entry->getContent()); + + $em->remove($entry); + $em->flush(); + + $client->getContainer()->get('craue_config')->set('download_images_enabled', 0); + } } -- cgit v1.2.3 From aedd6ca0fd212abd07ec59c5fd58ea2ca99198c5 Mon Sep 17 00:00:00 2001 From: Jeremy Benoist Date: Mon, 31 Oct 2016 13:29:33 +0100 Subject: Add translations & migration --- app/DoctrineMigrations/Version20161031132655.php | 44 ++++++++++++++++++++++ .../translations/CraueConfigBundle.da.yml | 1 + .../translations/CraueConfigBundle.de.yml | 1 + .../translations/CraueConfigBundle.en.yml | 1 + .../translations/CraueConfigBundle.es.yml | 1 + .../translations/CraueConfigBundle.fa.yml | 1 + .../translations/CraueConfigBundle.fr.yml | 1 + .../translations/CraueConfigBundle.it.yml | 1 + .../translations/CraueConfigBundle.oc.yml | 1 + .../translations/CraueConfigBundle.pl.yml | 1 + .../translations/CraueConfigBundle.ro.yml | 1 + .../translations/CraueConfigBundle.tr.yml | 1 + src/Wallabag/CoreBundle/Command/InstallCommand.php | 14 +------ .../DataFixtures/ORM/LoadSettingData.php | 12 +----- .../Event/Subscriber/DownloadImagesSubscriber.php | 16 ++------ 15 files changed, 62 insertions(+), 35 deletions(-) create mode 100644 app/DoctrineMigrations/Version20161031132655.php diff --git a/app/DoctrineMigrations/Version20161031132655.php b/app/DoctrineMigrations/Version20161031132655.php new file mode 100644 index 00000000..c7364428 --- /dev/null +++ b/app/DoctrineMigrations/Version20161031132655.php @@ -0,0 +1,44 @@ +container = $container; + } + + private function getTable($tableName) + { + return $this->container->getParameter('database_table_prefix') . $tableName; + } + + /** + * @param Schema $schema + */ + public function up(Schema $schema) + { + $this->addSql("INSERT INTO \"".$this->getTable('craue_config_setting')."\" (name, value, section) VALUES ('download_images_enabled', 0, 'misc')"); + } + + /** + * @param Schema $schema + */ + public function down(Schema $schema) + { + $this->abortIf($this->connection->getDatabasePlatform()->getName() == 'sqlite', 'Migration can only be executed safely on \'mysql\' or \'postgresql\'.'); + + $this->addSql("DELETE FROM \"".$this->getTable('craue_config_setting')."\" WHERE name = 'download_images_enabled';"); + } +} diff --git a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.da.yml b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.da.yml index 3e11d675..7c323783 100644 --- a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.da.yml +++ b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.da.yml @@ -29,3 +29,4 @@ piwik_enabled: Aktiver Piwik demo_mode_enabled: "Aktiver demo-indstilling? (anvendes kun til wallabags offentlige demo)" demo_mode_username: "Demobruger" # share_public: Allow public url for entries +# download_images_enabled: Download images locally diff --git a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.de.yml b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.de.yml index c74b5c1f..438eb74a 100644 --- a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.de.yml +++ b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.de.yml @@ -29,3 +29,4 @@ piwik_enabled: Piwik aktivieren demo_mode_enabled: "Test-Modus aktivieren? (nur für die öffentliche wallabag-Demo genutzt)" demo_mode_username: "Test-Benutzer" share_public: Erlaube eine öffentliche URL für Einträge +# download_images_enabled: Download images locally diff --git a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.en.yml b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.en.yml index 77c09db4..c2f2b3fb 100644 --- a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.en.yml +++ b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.en.yml @@ -29,3 +29,4 @@ piwik_enabled: Enable Piwik demo_mode_enabled: "Enable demo mode ? (only used for the wallabag public demo)" demo_mode_username: "Demo user" share_public: Allow public url for entries +download_images_enabled: Download images locally diff --git a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.es.yml b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.es.yml index baa83849..76feea50 100644 --- a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.es.yml +++ b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.es.yml @@ -29,3 +29,4 @@ piwik_enabled: Activar Piwik demo_mode_enabled: "Activar modo demo (sólo usado para la demo de wallabag)" demo_mode_username: "Nombre de usuario demo" # share_public: Allow public url for entries +# download_images_enabled: Download images locally diff --git a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.fa.yml b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.fa.yml index b394977e..30df0086 100644 --- a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.fa.yml +++ b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.fa.yml @@ -29,3 +29,4 @@ modify_settings: "اعمال" # demo_mode_enabled: "Enable demo mode ? (only used for the wallabag public demo)" # demo_mode_username: "Demo user" # share_public: Allow public url for entries +# download_images_enabled: Download images locally diff --git a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.fr.yml b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.fr.yml index 31a80880..a60341b3 100644 --- a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.fr.yml +++ b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.fr.yml @@ -29,3 +29,4 @@ piwik_enabled: Activer Piwik demo_mode_enabled: "Activer le mode démo ? (utiliser uniquement pour la démo publique de wallabag)" demo_mode_username: "Utilisateur de la démo" share_public: Autoriser une URL publique pour les articles +download_images_enabled: Télécharger les images en local diff --git a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.it.yml b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.it.yml index ba038556..3ad5f7d0 100644 --- a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.it.yml +++ b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.it.yml @@ -29,3 +29,4 @@ piwik_enabled: Abilita Piwik demo_mode_enabled: "Abilita modalità demo ? (usato solo per la demo pubblica di wallabag)" demo_mode_username: "Utente Demo" # share_public: Allow public url for entries +# download_images_enabled: Download images locally diff --git a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.oc.yml b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.oc.yml index 55249e33..fd83b437 100644 --- a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.oc.yml +++ b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.oc.yml @@ -29,3 +29,4 @@ piwik_enabled: Activar Piwik demo_mode_enabled: "Activar lo mode demostracion ? (utilizar solament per la demostracion publica de wallabag)" demo_mode_username: "Utilizaire de la demostracion" # share_public: Allow public url for entries +# download_images_enabled: Download images locally diff --git a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.pl.yml b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.pl.yml index 42cc5b52..3a63eebb 100644 --- a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.pl.yml +++ b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.pl.yml @@ -29,3 +29,4 @@ piwik_enabled: Włacz Piwik demo_mode_enabled: "Włacz tryb demo? (używany wyłącznie dla publicznej demonstracji Wallabag)" demo_mode_username: "Użytkownik Demonstracyjny" share_public: Zezwalaj na publiczny adres url dla wpisow +# download_images_enabled: Download images locally diff --git a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.ro.yml b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.ro.yml index 8e72b955..4fb42e98 100644 --- a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.ro.yml +++ b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.ro.yml @@ -29,3 +29,4 @@ modify_settings: "aplică" # demo_mode_enabled: "Enable demo mode ? (only used for the wallabag public demo)" # demo_mode_username: "Demo user" # share_public: Allow public url for entries +# download_images_enabled: Download images locally diff --git a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.tr.yml b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.tr.yml index 55f70843..ebfadf29 100644 --- a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.tr.yml +++ b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.tr.yml @@ -29,3 +29,4 @@ # demo_mode_enabled: "Enable demo mode ? (only used for the wallabag public demo)" # demo_mode_username: "Demo user" # share_public: Allow public url for entries +# download_images_enabled: Download images locally diff --git a/src/Wallabag/CoreBundle/Command/InstallCommand.php b/src/Wallabag/CoreBundle/Command/InstallCommand.php index aedccfe4..9fe90357 100644 --- a/src/Wallabag/CoreBundle/Command/InstallCommand.php +++ b/src/Wallabag/CoreBundle/Command/InstallCommand.php @@ -370,7 +370,7 @@ class InstallCommand extends ContainerAwareCommand ], [ 'name' => 'wallabag_url', - 'value' => 'http://v2.wallabag.org', + 'value' => '', 'section' => 'misc', ], [ @@ -401,17 +401,7 @@ class InstallCommand extends ContainerAwareCommand [ 'name' => 'download_images_enabled', 'value' => '0', - 'section' => 'image', - ], - [ - 'name' => 'download_images_with_rabbitmq', - 'value' => '0', - 'section' => 'image', - ], - [ - 'name' => 'download_images_with_redis', - 'value' => '0', - 'section' => 'image', + 'section' => 'misc', ], ]; diff --git a/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadSettingData.php b/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadSettingData.php index 70a7a4ac..d0085660 100644 --- a/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadSettingData.php +++ b/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadSettingData.php @@ -143,17 +143,7 @@ class LoadSettingData extends AbstractFixture implements OrderedFixtureInterface [ 'name' => 'download_images_enabled', 'value' => '0', - 'section' => 'image', - ], - [ - 'name' => 'download_images_with_rabbitmq', - 'value' => '0', - 'section' => 'image', - ], - [ - 'name' => 'download_images_with_redis', - 'value' => '0', - 'section' => 'image', + 'section' => 'misc', ], ]; diff --git a/src/Wallabag/CoreBundle/Event/Subscriber/DownloadImagesSubscriber.php b/src/Wallabag/CoreBundle/Event/Subscriber/DownloadImagesSubscriber.php index 3f2d460c..6fddcea9 100644 --- a/src/Wallabag/CoreBundle/Event/Subscriber/DownloadImagesSubscriber.php +++ b/src/Wallabag/CoreBundle/Event/Subscriber/DownloadImagesSubscriber.php @@ -111,6 +111,8 @@ class DownloadImagesSubscriber implements EventSubscriber /** * Download all images from the html. * + * @todo If we want to add async download, it should be done in that method + * * @param Config $config * @param Entry $entry * @@ -120,12 +122,6 @@ class DownloadImagesSubscriber implements EventSubscriber { $this->downloadImages->setWallabagUrl($config->get('wallabag_url')); - // if ($config->get('download_images_with_rabbitmq')) { - - // } else if ($config->get('download_images_with_redis')) { - - // } - return $this->downloadImages->processHtml( $entry->getContent(), $entry->getUrl() @@ -135,6 +131,8 @@ class DownloadImagesSubscriber implements EventSubscriber /** * Download the preview picture. * + * @todo If we want to add async download, it should be done in that method + * * @param Config $config * @param Entry $entry * @@ -144,12 +142,6 @@ class DownloadImagesSubscriber implements EventSubscriber { $this->downloadImages->setWallabagUrl($config->get('wallabag_url')); - // if ($config->get('download_images_with_rabbitmq')) { - - // } else if ($config->get('download_images_with_redis')) { - - // } - return $this->downloadImages->processSingleImage( $entry->getPreviewPicture(), $entry->getUrl() -- cgit v1.2.3 From e61ee56031aa0788f9a40ec245d3c391d219d6c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolas=20L=C5=93uillet?= Date: Mon, 31 Oct 2016 16:16:41 +0100 Subject: Added QRCode and link to configure android application --- app/Resources/static/themes/_global/js/tools.js | 2 ++ package.json | 3 ++ .../CoreBundle/Controller/ConfigController.php | 1 + .../views/themes/material/Config/index.html.twig | 12 +++++++ .../wallabagcore/themes/baggy/css/style.min.css | 2 +- .../wallabagcore/themes/baggy/js/baggy.min.js | 39 +++++++++++----------- .../wallabagcore/themes/material/css/style.min.css | 6 ++-- .../themes/material/js/material.min.js | 33 +++++++++--------- 8 files changed, 59 insertions(+), 39 deletions(-) diff --git a/app/Resources/static/themes/_global/js/tools.js b/app/Resources/static/themes/_global/js/tools.js index ab30deb1..00f5d501 100644 --- a/app/Resources/static/themes/_global/js/tools.js +++ b/app/Resources/static/themes/_global/js/tools.js @@ -1,5 +1,7 @@ const $ = require('jquery'); +var jrQrcode = require('jr-qrcode'); + function supportsLocalStorage() { try { return 'localStorage' in window && window.localStorage !== null; diff --git a/package.json b/package.json index 20afb425..e8cf58e5 100644 --- a/package.json +++ b/package.json @@ -99,5 +99,8 @@ "stylelint": "^7.3.1", "stylelint-config-standard": "^13.0.2", "through": "^2.3.8" + }, + "dependencies": { + "jr-qrcode": "^1.0.5" } } diff --git a/src/Wallabag/CoreBundle/Controller/ConfigController.php b/src/Wallabag/CoreBundle/Controller/ConfigController.php index 8d391917..d40efcd7 100644 --- a/src/Wallabag/CoreBundle/Controller/ConfigController.php +++ b/src/Wallabag/CoreBundle/Controller/ConfigController.php @@ -149,6 +149,7 @@ class ConfigController extends Controller 'token' => $config->getRssToken(), ], 'twofactor_auth' => $this->getParameter('twofactor_auth'), + 'wallabag_url' => $this->get('craue_config')->get('wallabag_url'), 'enabled_users' => $this->getDoctrine() ->getRepository('WallabagUserBundle:User') ->getSumEnabledUsers(), 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 b53ae2fe..3fbb49ea 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 @@ -71,6 +71,18 @@ +
+
+
Configure your Android application
+ Touch here to prefill your Android application + +
+ +
+ {{ form_widget(form.config.save, {'attr': {'class': 'btn waves-effect waves-light'}}) }} {{ form_rest(form.config) }} diff --git a/web/bundles/wallabagcore/themes/baggy/css/style.min.css b/web/bundles/wallabagcore/themes/baggy/css/style.min.css index c0ee23ba..dca6ba66 100644 --- a/web/bundles/wallabagcore/themes/baggy/css/style.min.css +++ b/web/bundles/wallabagcore/themes/baggy/css/style.min.css @@ -1 +1 @@ -@font-face{font-family:PT Sans;font-style:normal;font-weight:700;src:local("PT Sans Bold"),local("PTSans-Bold"),url(../fonts/ptsansbold.woff) format("woff")}html{min-height:100%}body{background-color:#eee}.login{background-color:#333}.login #main{padding:0;margin:0}.login form{background-color:#fff;padding:1.5em;box-shadow:0 1px 8px rgba(0,0,0,.9);width:20em;top:8em;margin-left:-10em}.login .logo,.login form{position:absolute;left:50%}.login .logo{top:2em;margin-left:-55px}::-moz-selection{color:#fff;background-color:#000}::selection{color:#fff;background-color:#000}.desktopHide{display:none}.logo{position:fixed;z-index:5;top:.4em;left:.6em}h2,h3,h4{font-family:PT Sans,sans-serif;text-transform:uppercase}label,li,p{color:#666}a{color:#000;font-weight:700}a.nostyle,a:focus,a:hover{text-decoration:none}form fieldset{border:0;padding:0;margin:0}form input[type=email],form input[type=number],form input[type=password],form input[type=text],form input[type=url],select{border:1px solid #999;padding:.5em 1em;min-width:12em;color:#666}@media screen and (-webkit-min-device-pixel-ratio:0){select{-webkit-appearance:none;border-radius:0;background:#fff url(../../_global/img/bg-select.png) no-repeat 100%}}.inline .row{display:inline-block;margin-right:.5em}.inline label{min-width:6em}fieldset label{display:inline-block;min-width:12.5em;color:#666}label{margin-right:.5em}form .row{margin-bottom:.5em}form button,input[type=submit]{cursor:pointer;background-color:#000;color:#fff;padding:.5em 1em;display:inline-block;border:1px solid #000}form button:focus,form button:hover,input[type=submit]:focus,input[type=submit]:hover{background-color:#fff;color:#000;transition:all .5s ease}#bookmarklet{cursor:move}h2:after{content:"";height:4px;width:70px;background-color:#000;display:block}.links,.links li{padding:0;margin:0}.links li{list-style:none}#links{position:fixed;top:0;width:10em;left:0;text-align:right;background-color:#333;padding-top:9.5em;height:100%;box-shadow:inset -4px 0 20px rgba(0,0,0,.6);z-index:4}#main{margin-left:12em;position:relative;z-index:1;padding-right:5%;padding-bottom:1em}#links>li>a{display:block;padding:.5em 2em .5em 1em;color:#fff;position:relative;text-transform:uppercase;text-decoration:none;font-weight:400;font-family:PT Sans,sans-serif;transition:all .5s ease}#links>li>a:focus,#links>li>a:hover{background-color:#999;color:#000}#links .current:after{content:"";width:0;height:0;position:absolute;border-style:solid;border-width:10px;border-color:transparent #eee transparent transparent;right:0;top:50%;margin-top:-10px}#links li:last-child{position:fixed;bottom:1em;width:10em}#links li:last-child a:before{font-size:1.2em;position:relative;top:2px}#sort{padding:0;list-style-type:none;opacity:.5;display:inline-block}#sort li{display:inline;font-size:.9em}#sort li+li{margin-left:10px}#sort a{padding:2px 2px 0;vertical-align:middle}#sort img{vertical-align:baseline}#sort img:hover{cursor:pointer}#display-mode{float:right;margin-top:10px;margin-bottom:10px;opacity:.5}#listmode{width:16px;display:inline-block;text-decoration:none}#listmode a:hover{opacity:1}#listmode.tablemode{background-image:url(../img/baggy/table.png)}#listmode.listmode,#listmode.tablemode{background-repeat:no-repeat;background-position:bottom}#listmode.listmode{background-image:url(../img/baggy/list.png)}#warning_message{position:fixed;background-color:tomato;z-index:7;bottom:0;left:0;width:100%;color:#000}#content{margin-top:2em;min-height:30em}footer{text-align:right;position:relative;bottom:0;right:5em;color:#999;font-size:.8em;font-style:italic;z-index:5}footer a{color:#999;font-weight:400}.list-entries{letter-spacing:-5px}.listmode .entry{width:100%!important;margin-left:0!important}.card-entry-labels{position:absolute;top:100px;left:-1em;z-index:6;max-width:50%;padding-left:0}.card-entry-labels li{margin:10px 10px 10px auto;padding:5px 12px 5px 25px;background-color:rgba(0,0,0,.6);border-radius:0 3px 3px 0;color:#fff;cursor:default;max-height:2em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.card-entry-tags{max-height:2em;overflow-y:hidden;padding:0;margin:0}.card-entry-tags li,.card-entry-tags span{display:inline-block;margin:0 5px;padding:5px 12px;background-color:rgba(0,0,0,.6);border-radius:3px;max-height:2em;overflow:hidden;text-overflow:ellipsis}.card-entry-labels a,.card-entry-tags a{text-decoration:none;font-weight:400;color:#fff}.nav-panel-add-tag{margin-top:10px}.list-entries+.results{margin-bottom:2em}.estimatedTime .reading-time{color:#999;font-style:italic;font-weight:400;font-size:.9em}.estimatedTime small{position:relative;top:-1px}.entry{background-color:#fff;letter-spacing:normal;box-shadow:0 3px 7px rgba(0,0,0,.3);display:inline-block;width:32%;margin-bottom:1.5em;vertical-align:top;margin-right:1%;position:relative;overflow:hidden;padding:1.5em 1.5em 3em;height:440px}.entry:before{width:0;height:0;border-style:solid;border-color:transparent transparent #000;border-width:10px;bottom:.3em;z-index:1;right:1.5em}.entry:after,.entry:before{content:"";position:absolute;transition:all .5s ease}.entry:after{height:7px;width:100%;bottom:0;left:0;background-color:#000}.entry:hover{box-shadow:0 3px 10px #000}.entry:hover:after{height:40px}.entry:hover:before{bottom:2.4em}.entry:hover h2 a{color:#666}.entry h2{text-transform:none;margin-bottom:0;line-height:1.2}.entry h2:after{content:none}.entry h2 a{display:block;text-decoration:none;color:#000;word-wrap:break-word;transition:all .5s ease}img.preview{max-width:calc(100% + 3em);left:-1.5em;position:relative}.entry p{color:#666;font-size:.9em;line-height:1.7;margin-top:5px}.entry h2 a:first-letter{text-transform:uppercase}.entry:hover .tools{bottom:0}.entry .tools{position:absolute;bottom:-50px;left:0;width:100%;z-index:1;padding-right:.5em;text-align:right;transition:all .5s ease}.entry .tools a{color:#666;text-decoration:none;display:block;padding:.4em}.entry .tools a:hover{color:#fff}.entry .tools li{display:inline-block}.entry:nth-child(3n+1){margin-left:0}.results{letter-spacing:-5px;padding:0 0 .5em}.results>*{display:inline-block;vertical-align:top;letter-spacing:normal;width:50%;text-align:right}div.pagination ul{text-align:right;margin-bottom:50px}.nb-results{text-align:left;font-style:italic;color:#999}div.pagination ul>*{display:inline-block;margin-left:.5em}div.pagination ul a{color:#999;text-decoration:none}div.pagination ul a:focus,div.pagination ul a:hover{text-decoration:underline}div.pagination ul .next.disabled,div.pagination ul .prev.disabled{display:none}div.pagination ul .current{height:25px;padding:4px 8px;border:1px solid #d5d5d5;text-decoration:none;font-weight:700;color:#000;background-color:#ccc}.popup-form{background:rgba(0,0,0,.5);left:10em;height:100%;width:100%;margin:0;margin-top:-30%!important;display:none;border-left:1px solid #eee}.popup-form,.popup-form form{position:absolute;top:0;z-index:5;padding:2em}.popup-form form{background-color:#fff;left:0;border:10px solid #000;width:400px;height:200px}#bagit-form-form .addurl{margin-left:0}.close-button,.closeMessage{background-color:#000;color:#fff;font-size:1.2em;line-height:1.6;width:1.6em;height:1.6em;text-align:center;text-decoration:none}.close-button:focus,.close-button:hover,.closeMessage:focus,.closeMessage:hover{background-color:#999;color:#000}.close-button--popup{display:inline-block;position:absolute;top:0;right:0;font-size:1.4em}.active-current{background-color:#999}.active-current:after{content:"";width:0;height:0;position:absolute;border-style:solid;border-width:10px;border-color:transparent #eee transparent transparent;right:0;top:50%;margin-top:-10px}.opacity03{opacity:.3}.add-to-wallabag-link-after{background-color:#000;color:#fff;padding:0 3px 2px}a.add-to-wallabag-link-after{visibility:hidden;position:absolute;opacity:0;transition-duration:2s;transition-timing-function:ease-out}#article article a:hover+a.add-to-wallabag-link-after,a.add-to-wallabag-link-after:hover{opacity:1;visibility:visible;transition-duration:.3s;transition-timing-function:ease-in}a.add-to-wallabag-link-after:after{content:"w"}#add-link-result{font-weight:700;font-size:.9em}.btn-clickable{cursor:pointer}@font-face{font-family:icomoon;src:url(../fonts/IcoMoon-Free.ttf);font-weight:400;font-style:normal}@font-face{font-family:Material Icons;font-style:normal;font-weight:400;src:url(../fonts/MaterialIcons-Regular.eot);src:local("Material Icons"),local("MaterialIcons-Regular"),url(../fonts/MaterialIcons-Regular.woff2) format("woff2"),url(../fonts/MaterialIcons-Regular.woff) format("woff"),url(../fonts/MaterialIcons-Regular.ttf) format("truetype")}.material-icons{font-family:Material Icons;font-weight:400;font-style:normal;font-size:1em;width:1em;height:1em;display:inline-block;line-height:1;text-transform:none;letter-spacing:normal;word-wrap:normal;white-space:nowrap;direction:ltr;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;-moz-osx-font-smoothing:grayscale;font-feature-settings:'liga'}.material-icons.md-18{font-size:18px}.material-icons.md-24{font-size:24px}.material-icons.md-36{font-size:36px}.material-icons.md-48{font-size:48px}.icon-image span,.icon span{position:absolute;top:-9999px}[class*=" icon-"]:before,[class^=icon-]:before{font-family:icomoon;speak:none;font-style:normal;font-weight:400;font-variant:normal;text-transform:none;line-height:1;letter-spacing:0;-ms-font-feature-settings:"liga" 1;-o-font-feature-settings:"liga";font-feature-settings:"liga";-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.icon-flattr:before{content:"\ead4"}.icon-mail:before{content:"\ea86"}.icon-up-open:before{content:"\e80b"}.icon-star:before{content:"\e9d9"}.icon-check:before{content:"\ea10"}.icon-link:before{content:"\e9cb"}.icon-reply:before{content:"\e806"}.icon-menu:before{content:"\e9bd"}.icon-clock:before{content:"\e803"}.icon-twitter:before{content:"\ea96"}.icon-down-open:before{content:"\e809"}.icon-trash:before{content:"\e9ac"}.icon-delete:before{content:"\ea0d"}.icon-power:before{content:"\ea14"}.icon-arrow-up-thick:before{content:"\ea3a"}.icon-rss:before{content:"\e808"}.icon-print:before{content:"\e954"}.icon-reload:before{content:"\ea2e"}.icon-price-tags:before{content:"\e936"}.icon-eye:before{content:"\e9ce"}.icon-no-eye:before{content:"\e9d1"}.icon-calendar:before{content:"\e953"}.icon-time:before{content:"\e952"}.icon-image{background-size:16px 16px;background-repeat:no-repeat;background-position:50%;padding-right:1em!important;padding-left:1em!important}.icon-image--carrot{background-image:url(../../_global/img/icons/carrot-icon--white.png)}.icon-image--diaspora{background-image:url(../../_global/img/icons/diaspora-icon--black.png)}.icon-image--shaarli{background-image:url(../../_global/img/icons/shaarli.png)}.icon-check.archive:before,.icon-star.fav:before{color:#fff}.messages{text-align:left;margin-top:1em}.messages>*{display:inline-block}.warning{font-weight:700;display:block;width:100%}.more-info{font-size:.85em;line-height:1.5;color:#aaa}.more-info a{color:#aaa}#article{width:70%;margin-bottom:3em;text-align:justify}#article .tags{margin-bottom:1em}#article i{font-style:normal}blockquote{border:1px solid #999;background-color:#fff;padding:1em;margin:0}#article h1{text-align:left}#article h2,#article h3,#article h4{text-transform:none}#article h2:after{content:none}.topPosF{position:fixed;right:20%;bottom:2em;font-size:1.5em}#article_toolbar{margin-bottom:1em}#article_toolbar li{display:inline-block;margin:3px auto}#article_toolbar a{background-color:#000;padding:.3em .5em .2em;color:#fff;text-decoration:none}#article_toolbar a:focus,#article_toolbar a:hover{background-color:#999}#nav-btn-add-tag{cursor:pointer}.shaarli:before{content:"*"}.return{text-decoration:none;margin-top:1em;display:block}.return:before{margin-right:.5em}.notags{font-style:italic;color:#999}.icon-rss{background-color:#000;color:#fff;padding:.2em .5em}.icon-rss:before{position:relative;top:2px}.list-tags li{margin-bottom:.5em}.list-tags .icon-rss:focus,.list-tags .icon-rss:hover{background-color:#fff;color:#000;text-decoration:none}.list-tags a{text-decoration:none}.list-tags a:focus,.list-tags a:hover{text-decoration:underline}pre code{font-family:Courier New,Courier,monospace}#filters{position:fixed;width:20%;height:100%;top:0;right:0;background-color:#fff;padding:15px;padding-right:30px;padding-top:30px;border-left:1px solid #333;z-index:3;min-width:300px}#filters form .filter-group{margin:5px}#download-form{position:fixed;width:10%;height:100%;top:0;right:0;background-color:#fff;padding:15px;padding-right:30px;padding-top:30px;border-left:1px solid #333;z-index:3;min-width:200px}#download-form li{display:block;padding:.5em 2em .5em 1em;color:#fff;position:relative;text-transform:uppercase;text-decoration:none;font-weight:400;font-family:PT Sans,sans-serif;transition:all .5s ease}@media screen and (max-width:1050px){.entry{width:49%}.entry:nth-child(3n+1){margin-left:1.5%}.entry:nth-child(2n+1){margin-left:0}}@media screen and (max-width:900px){#article{width:80%}.topPosF{right:2.5em}}@media screen and (max-width:700px){.entry{width:100%;margin-left:0}#display-mode{display:none}}@media screen and (max-height:770px){.menu.developer,.menu.internal,.menu.users{display:none}}@media screen and (max-width:500px){.entry{width:100%;margin-left:0}body>header{background-color:#333;position:fixed;top:0;width:100%;height:3em;z-index:2}#links li:last-child{position:static;width:auto}#links li:last-child a:before{content:none}.logo{width:1.25em;height:1.25em;left:0;top:0}.login>header,.login form{position:static}.login form{width:100%;margin-left:0}.login .logo{height:auto;top:.5em;width:75px;margin-left:-37.5px}.desktopHide{display:block;position:fixed;z-index:5;top:0;right:0;border:0;width:2.5em;height:2.5em;cursor:pointer;background-color:#999;font-size:1.2em}.desktopHide:focus,.desktopHide:hover{background-color:#fff}#links{display:none;width:100%;height:auto;padding-top:3em}#links.menu--open{display:block}footer{margin-right:3em}#main,footer{position:static}#main{margin-left:1.5em;padding-right:1.5em;margin-top:3em}#article_toolbar .topPosF,.card-entry-labels{display:none}#article{width:100%}#article h1{font-size:1.5em}#article_toolbar a{padding:.3em .4em .2em}#display-mode{display:none}#bagit-form,#search-form,.popup-form{left:0;width:100%;border-left:none}#bagit-form form,#search-form form,.popup-form form{width:100%}}.messages.error.install{border:1px solid #c42608;color:#c00!important;background:#fff0ef;text-align:left}.messages.notice.install{border:1px solid #ebcd41;color:#000;background:#fffcd3;text-align:left}.messages.success.install{border:1px solid #6dc70c;background:#e0fbcc!important;text-align:left}@media print{body{font-family:Serif;background-color:#fff}@page{margin:1cm}img{max-width:100%!important}#article-informations,#article .mbm a,#article_toolbar,#links,#sort,.entrie+.results,.messages,.top_link,body>footer,body>header,div.tools,header div{display:none!important}article{border:none!important}.vieworiginal a:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}.pagination span.current{border-style:dashed}#main{padding:0;margin:0;margin-left:0;padding-right:0;padding-bottom:0}#article,#main{width:100%}}*{box-sizing:border-box}html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{font-size:1em;line-height:1.5;margin:0}dl:first-child,h1:first-child,h2:first-child,h3:first-child,h4:first-child,h5:first-child,h6:first-child,ol:first-child,p:first-child,ul:first-child{margin-top:0}code,kbd,pre,samp{font-family:monospace,serif}pre{white-space:pre-wrap}.upper{text-transform:uppercase}.bold{font-weight:700}.inner{margin:0 auto;max-width:61.25em}figure,img,table{max-width:100%;height:auto}iframe{max-width:100%}.fl{float:left}.fr{float:right}table{border-collapse:collapse}figure{margin:0}button,input,select,textarea{font-family:inherit;font-size:100%;margin:0}input[type=search]{-webkit-appearance:textfield}.dib{display:inline-block;vertical-align:middle}.dnone{display:none}.dtable{display:table}.dtable>*{display:table-row}.dtable>*>*{display:table-cell}.element-invisible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.small{font-size:.8em}.big{font-size:1.2em}.w100{width:100%}.w90{width:90%}.w80{width:80%}.w70{width:70%}.w60{width:60%}.w50{width:50%}.w40{width:40%}.w30{width:30%}.w20{width:20%}.w10{width:10%}article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,video{display:inline-block}@media screen and (-webkit-min-device-pixel-ratio:0){select{-webkit-appearance:none;border-radius:0}} \ No newline at end of file +@font-face{font-family:PT Sans;font-style:normal;font-weight:700;src:local("PT Sans Bold"),local("PTSans-Bold"),url(../fonts/ptsansbold.woff) format("woff")}html{min-height:100%}body{background-color:#eee}.login{background-color:#333}.login #main{padding:0;margin:0}.login form{background-color:#fff;padding:1.5em;box-shadow:0 1px 8px rgba(0,0,0,.9);width:20em;top:8em;margin-left:-10em}.login .logo,.login form{position:absolute;left:50%}.login .logo{top:2em;margin-left:-55px}::-moz-selection{color:#fff;background-color:#000}::selection{color:#fff;background-color:#000}.desktopHide{display:none}.logo{position:fixed;z-index:5;top:.4em;left:.6em}h2,h3,h4{font-family:PT Sans,sans-serif;text-transform:uppercase}label,li,p{color:#666}a{color:#000;font-weight:700}a.nostyle,a:focus,a:hover{text-decoration:none}form fieldset{border:0;padding:0;margin:0}form input[type=email],form input[type=number],form input[type=password],form input[type=text],form input[type=url],select{border:1px solid #999;padding:.5em 1em;min-width:12em;color:#666}@media screen and (-webkit-min-device-pixel-ratio:0){select{-webkit-appearance:none;border-radius:0;background:#fff url(../../_global/img/bg-select.png) no-repeat 100%}}.inline .row{display:inline-block;margin-right:.5em}.inline label{min-width:6em}fieldset label{display:inline-block;min-width:12.5em;color:#666}label{margin-right:.5em}form .row{margin-bottom:.5em}form button,input[type=submit]{cursor:pointer;background-color:#000;color:#fff;padding:.5em 1em;display:inline-block;border:1px solid #000}form button:focus,form button:hover,input[type=submit]:focus,input[type=submit]:hover{background-color:#fff;color:#000;transition:all .5s ease}#bookmarklet{cursor:move}h2:after{content:"";height:4px;width:70px;background-color:#000;display:block}.links,.links li{padding:0;margin:0}.links li{list-style:none}#links{position:fixed;top:0;width:10em;left:0;text-align:right;background-color:#333;padding-top:9.5em;height:100%;box-shadow:inset -4px 0 20px rgba(0,0,0,.6);z-index:4}#main{margin-left:12em;position:relative;z-index:1;padding-right:5%;padding-bottom:1em}#links>li>a{display:block;padding:.5em 2em .5em 1em;color:#fff;position:relative;text-transform:uppercase;text-decoration:none;font-weight:400;font-family:PT Sans,sans-serif;transition:all .5s ease}#links>li>a:focus,#links>li>a:hover{background-color:#999;color:#000}#links .current:after{content:"";width:0;height:0;position:absolute;border-style:solid;border-width:10px;border-color:transparent #eee transparent transparent;right:0;top:50%;margin-top:-10px}#links li:last-child{position:fixed;bottom:1em;width:10em}#links li:last-child a:before{font-size:1.2em;position:relative;top:2px}#sort{padding:0;list-style-type:none;opacity:.5;display:inline-block}#sort li{display:inline;font-size:.9em}#sort li+li{margin-left:10px}#sort a{padding:2px 2px 0;vertical-align:middle}#sort img{vertical-align:baseline}#sort img:hover{cursor:pointer}#display-mode{float:right;margin-top:10px;margin-bottom:10px;opacity:.5}#listmode{width:16px;display:inline-block;text-decoration:none}#listmode a:hover{opacity:1}#listmode.tablemode{background-image:url(../img/baggy/table.png)}#listmode.listmode,#listmode.tablemode{background-repeat:no-repeat;background-position:bottom}#listmode.listmode{background-image:url(../img/baggy/list.png)}#warning_message{position:fixed;background-color:tomato;z-index:7;bottom:0;left:0;width:100%;color:#000}#content{margin-top:2em;min-height:30em}footer{text-align:right;position:relative;bottom:0;right:5em;color:#999;font-size:.8em;font-style:italic;z-index:5}footer a{color:#999;font-weight:400}.list-entries{letter-spacing:-5px}.listmode .entry{width:100%!important;margin-left:0!important}.card-entry-labels{position:absolute;top:100px;left:-1em;z-index:6;max-width:50%;padding-left:0}.card-entry-labels li{margin:10px 10px 10px auto;padding:5px 12px 5px 25px;background-color:rgba(0,0,0,.6);border-radius:0 3px 3px 0;color:#fff;cursor:default;max-height:2em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.card-entry-tags{max-height:2em;overflow-y:hidden;padding:0;margin:0}.card-entry-tags li,.card-entry-tags span{display:inline-block;margin:0 5px;padding:5px 12px;background-color:rgba(0,0,0,.6);border-radius:3px;max-height:2em;overflow:hidden;text-overflow:ellipsis}.card-entry-labels a,.card-entry-tags a{text-decoration:none;font-weight:400;color:#fff}.nav-panel-add-tag{margin-top:10px}.list-entries+.results{margin-bottom:2em}.estimatedTime .reading-time{color:#999;font-style:italic;font-weight:400;font-size:.9em}.estimatedTime small{position:relative;top:-1px}.entry{background-color:#fff;letter-spacing:normal;box-shadow:0 3px 7px rgba(0,0,0,.3);display:inline-block;width:32%;margin-bottom:1.5em;vertical-align:top;margin-right:1%;position:relative;overflow:hidden;padding:1.5em 1.5em 3em;height:440px}.entry:before{width:0;height:0;border-style:solid;border-color:transparent transparent #000;border-width:10px;bottom:.3em;z-index:1;right:1.5em}.entry:after,.entry:before{content:"";position:absolute;transition:all .5s ease}.entry:after{height:7px;width:100%;bottom:0;left:0;background-color:#000}.entry:hover{box-shadow:0 3px 10px #000}.entry:hover:after{height:40px}.entry:hover:before{bottom:2.4em}.entry:hover h2 a{color:#666}.entry h2{text-transform:none;margin-bottom:0;line-height:1.2}.entry h2:after{content:none}.entry h2 a{display:block;text-decoration:none;color:#000;word-wrap:break-word;transition:all .5s ease}img.preview{max-width:calc(100% + 3em);left:-1.5em;position:relative}.entry p{color:#666;font-size:.9em;line-height:1.7;margin-top:5px}.entry h2 a:first-letter{text-transform:uppercase}.entry:hover .tools{bottom:0}.entry .tools{position:absolute;bottom:-50px;left:0;width:100%;z-index:1;padding-right:.5em;text-align:right;transition:all .5s ease}.entry .tools a{color:#666;text-decoration:none;display:block;padding:.4em}.entry .tools a:hover{color:#fff}.entry .tools li{display:inline-block}.entry:nth-child(3n+1){margin-left:0}.results{letter-spacing:-5px;padding:0 0 .5em}.results>*{display:inline-block;vertical-align:top;letter-spacing:normal;width:50%;text-align:right}div.pagination ul{text-align:right;margin-bottom:50px}.nb-results{text-align:left;font-style:italic;color:#999}div.pagination ul>*{display:inline-block;margin-left:.5em}div.pagination ul a{color:#999;text-decoration:none}div.pagination ul a:focus,div.pagination ul a:hover{text-decoration:underline}div.pagination ul .next.disabled,div.pagination ul .prev.disabled{display:none}div.pagination ul .current{height:25px;padding:4px 8px;border:1px solid #d5d5d5;text-decoration:none;font-weight:700;color:#000;background-color:#ccc}.popup-form{background:rgba(0,0,0,.5);left:10em;height:100%;width:100%;margin:0;margin-top:-30%!important;display:none;border-left:1px solid #eee}.popup-form,.popup-form form{position:absolute;top:0;z-index:5;padding:2em}.popup-form form{background-color:#fff;left:0;border:10px solid #000;width:400px;height:200px}#bagit-form-form .addurl{margin-left:0}.close-button,.closeMessage{background-color:#000;color:#fff;font-size:1.2em;line-height:1.6;width:1.6em;height:1.6em;text-align:center;text-decoration:none}.close-button:focus,.close-button:hover,.closeMessage:focus,.closeMessage:hover{background-color:#999;color:#000}.close-button--popup{display:inline-block;position:absolute;top:0;right:0;font-size:1.4em}.active-current{background-color:#999}.active-current:after{content:"";width:0;height:0;position:absolute;border-style:solid;border-width:10px;border-color:transparent #eee transparent transparent;right:0;top:50%;margin-top:-10px}.opacity03{opacity:.3}.add-to-wallabag-link-after{background-color:#000;color:#fff;padding:0 3px 2px}a.add-to-wallabag-link-after{visibility:hidden;position:absolute;opacity:0;transition-duration:2s;transition-timing-function:ease-out}#article article a:hover+a.add-to-wallabag-link-after,a.add-to-wallabag-link-after:hover{opacity:1;visibility:visible;transition-duration:.3s;transition-timing-function:ease-in}a.add-to-wallabag-link-after:after{content:"w"}#add-link-result{font-weight:700;font-size:.9em}.btn-clickable{cursor:pointer}@font-face{font-family:icomoon;src:url(../fonts/IcoMoon-Free.ttf);font-weight:400;font-style:normal}@font-face{font-family:Material Icons;font-style:normal;font-weight:400;src:url(../fonts/MaterialIcons-Regular.eot);src:local("Material Icons"),local("MaterialIcons-Regular"),url(../fonts/MaterialIcons-Regular.woff2) format("woff2"),url(../fonts/MaterialIcons-Regular.woff) format("woff"),url(../fonts/MaterialIcons-Regular.ttf) format("truetype")}.material-icons{font-family:Material Icons;font-weight:400;font-style:normal;font-size:1em;width:1em;height:1em;display:inline-block;line-height:1;text-transform:none;letter-spacing:normal;word-wrap:normal;white-space:nowrap;direction:ltr;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;-moz-osx-font-smoothing:grayscale;font-feature-settings:'liga'}.material-icons.md-18{font-size:18px}.material-icons.md-24{font-size:24px}.material-icons.md-36{font-size:36px}.material-icons.md-48{font-size:48px}.icon-image span,.icon span{position:absolute;top:-9999px}[class*=" icon-"]:before,[class^=icon-]:before{font-family:icomoon;speak:none;font-style:normal;font-weight:400;font-variant:normal;text-transform:none;line-height:1;letter-spacing:0;-ms-font-feature-settings:"liga" 1;-o-font-feature-settings:"liga";font-feature-settings:"liga";-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.icon-flattr:before{content:"\ead4"}.icon-mail:before{content:"\ea86"}.icon-up-open:before{content:"\e80b"}.icon-star:before{content:"\e9d9"}.icon-check:before{content:"\ea10"}.icon-link:before{content:"\e9cb"}.icon-reply:before{content:"\e806"}.icon-menu:before{content:"\e9bd"}.icon-clock:before{content:"\e803"}.icon-twitter:before{content:"\ea96"}.icon-down-open:before{content:"\e809"}.icon-trash:before{content:"\e9ac"}.icon-delete:before{content:"\ea0d"}.icon-power:before{content:"\ea14"}.icon-arrow-up-thick:before{content:"\ea3a"}.icon-rss:before{content:"\e808"}.icon-print:before{content:"\e954"}.icon-reload:before{content:"\ea2e"}.icon-price-tags:before{content:"\e936"}.icon-eye:before{content:"\e9ce"}.icon-no-eye:before{content:"\e9d1"}.icon-calendar:before{content:"\e953"}.icon-time:before{content:"\e952"}.icon-image{background-size:16px 16px;background-repeat:no-repeat;background-position:50%;padding-right:1em!important;padding-left:1em!important}.icon-image--carrot{background-image:url(../../_global/img/icons/carrot-icon--white.png)}.icon-image--diaspora{background-image:url(../../_global/img/icons/diaspora-icon--black.png)}.icon-image--shaarli{background-image:url(../../_global/img/icons/shaarli.png)}.icon-check.archive:before,.icon-star.fav:before{color:#fff}.messages{text-align:left;margin-top:1em}.messages>*{display:inline-block}.warning{font-weight:700;display:block;width:100%}.more-info{font-size:.85em;line-height:1.5;color:#aaa}.more-info a{color:#aaa}#article{width:70%;margin-bottom:3em;text-align:justify}#article .tags{margin-bottom:1em}#article i{font-style:normal}blockquote{border:1px solid #999;background-color:#fff;padding:1em;margin:0}#article h1{text-align:left}#article h2,#article h3,#article h4{text-transform:none}#article h2:after{content:none}.topPosF{position:fixed;right:20%;bottom:2em;font-size:1.5em}#article_toolbar{margin-bottom:1em}#article_toolbar li{display:inline-block;margin:3px auto}#article_toolbar a{background-color:#000;padding:.3em .5em .2em;color:#fff;text-decoration:none}#article_toolbar a:focus,#article_toolbar a:hover{background-color:#999}#nav-btn-add-tag{cursor:pointer}.shaarli:before{content:"*"}.return{text-decoration:none;margin-top:1em;display:block}.return:before{margin-right:.5em}.notags{font-style:italic;color:#999}.icon-rss{background-color:#000;color:#fff;padding:.2em .5em}.icon-rss:before{position:relative;top:2px}.list-tags li{margin-bottom:.5em}.list-tags .icon-rss:focus,.list-tags .icon-rss:hover{background-color:#fff;color:#000;text-decoration:none}.list-tags a{text-decoration:none}.list-tags a:focus,.list-tags a:hover{text-decoration:underline}pre code{font-family:Courier New,Courier,monospace}#filters{position:fixed;width:20%;height:100%;top:0;right:0;background-color:#fff;padding:15px;padding-right:30px;padding-top:30px;border-left:1px solid #333;z-index:3;min-width:300px}#filters form .filter-group{margin:5px}#download-form{position:fixed;width:10%;height:100%;top:0;right:0;background-color:#fff;padding:15px;padding-right:30px;padding-top:30px;border-left:1px solid #333;z-index:3;min-width:200px}#download-form li{display:block;padding:.5em 2em .5em 1em;color:#fff;position:relative;text-transform:uppercase;text-decoration:none;font-weight:400;font-family:PT Sans,sans-serif;transition:all .5s ease}@media screen and (max-width:1050px){.entry{width:49%}.entry:nth-child(3n+1){margin-left:1.5%}.entry:nth-child(2n+1){margin-left:0}}@media screen and (max-width:900px){#article{width:80%}.topPosF{right:2.5em}}@media screen and (max-width:700px){.entry{width:100%;margin-left:0}#display-mode{display:none}}@media screen and (max-height:770px){.menu.developer,.menu.internal,.menu.users{display:none}}@media screen and (max-width:500px){.entry{width:100%;margin-left:0}body>header{background-color:#333;position:fixed;top:0;width:100%;height:3em;z-index:2}#links li:last-child{position:static;width:auto}#links li:last-child a:before{content:none}.logo{width:1.25em;height:1.25em;left:0;top:0}.login>header,.login form{position:static}.login form{width:100%;margin-left:0}.login .logo{height:auto;top:.5em;width:75px;margin-left:-37.5px}.desktopHide{display:block;position:fixed;z-index:5;top:0;right:0;border:0;width:2.5em;height:2.5em;cursor:pointer;background-color:#999;font-size:1.2em}.desktopHide:focus,.desktopHide:hover{background-color:#fff}#links{display:none;width:100%;height:auto;padding-top:3em}#links.menu--open{display:block}footer{margin-right:3em}#main,footer{position:static}#main{margin-left:1.5em;padding-right:1.5em;margin-top:3em}#article_toolbar .topPosF,.card-entry-labels{display:none}#article{width:100%}#article h1{font-size:1.5em}#article_toolbar a{padding:.3em .4em .2em}#display-mode{display:none}#bagit-form,#search-form,.popup-form{left:0;width:100%;border-left:none}#bagit-form form,#search-form form,.popup-form form{width:100%}};.messages.error.install{border:1px solid #c42608;color:#c00!important;background:#fff0ef;text-align:left}.messages.notice.install{border:1px solid #ebcd41;color:#000;background:#fffcd3;text-align:left}.messages.success.install{border:1px solid #6dc70c;background:#e0fbcc!important;text-align:left};@media print{body{font-family:Serif;background-color:#fff}@page{margin:1cm}img{max-width:100%!important}#article-informations,#article .mbm a,#article_toolbar,#links,#sort,.entrie+.results,.messages,.top_link,body>footer,body>header,div.tools,header div{display:none!important}article{border:none!important}.vieworiginal a:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}.pagination span.current{border-style:dashed}#main{padding:0;margin:0;margin-left:0;padding-right:0;padding-bottom:0}#article,#main{width:100%}}*{box-sizing:border-box}html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{font-size:1em;line-height:1.5;margin:0}dl:first-child,h1:first-child,h2:first-child,h3:first-child,h4:first-child,h5:first-child,h6:first-child,ol:first-child,p:first-child,ul:first-child{margin-top:0}code,kbd,pre,samp{font-family:monospace,serif}pre{white-space:pre-wrap}.upper{text-transform:uppercase}.bold{font-weight:700}.inner{margin:0 auto;max-width:61.25em}figure,img,table{max-width:100%;height:auto}iframe{max-width:100%}.fl{float:left}.fr{float:right}table{border-collapse:collapse}figure{margin:0}button,input,select,textarea{font-family:inherit;font-size:100%;margin:0}input[type=search]{-webkit-appearance:textfield}.dib{display:inline-block;vertical-align:middle}.dnone{display:none}.dtable{display:table}.dtable>*{display:table-row}.dtable>*>*{display:table-cell}.element-invisible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.small{font-size:.8em}.big{font-size:1.2em}.w100{width:100%}.w90{width:90%}.w80{width:80%}.w70{width:70%}.w60{width:60%}.w50{width:50%}.w40{width:40%}.w30{width:30%}.w20{width:20%}.w10{width:10%}article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,video{display:inline-block}@media screen and (-webkit-min-device-pixel-ratio:0){select{-webkit-appearance:none;border-radius:0}} \ No newline at end of file diff --git a/web/bundles/wallabagcore/themes/baggy/js/baggy.min.js b/web/bundles/wallabagcore/themes/baggy/js/baggy.min.js index ddb564d8..da4ad4e0 100644 --- a/web/bundles/wallabagcore/themes/baggy/js/baggy.min.js +++ b/web/bundles/wallabagcore/themes/baggy/js/baggy.min.js @@ -1,19 +1,20 @@ -!function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);var j=new Error("Cannot find module '"+g+"'");throw j.code="MODULE_NOT_FOUND",j}var k=c[g]={exports:{}};b[g][0].call(k.exports,function(a){var c=b[g][1][a];return e(c?c:a)},k,k.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;g li > a").removeClass("active-current"),e("#links > li > a").removeClass("current"),e("[id$=-arrow]").removeClass("arrow-down"),e("#content").removeClass("opacity03")}var j=e("#listmode"),k=e("#list-entries");e("#menu").click(function(){e("#links").toggleClass("menu--open");var a=e("#content");a.hasClass("opacity03")&&a.removeClass("opacity03")}),j.click(function(){1===e.cookie("listmode")?(e.removeCookie("listmode"),k.removeClass("listmode"),j.removeClass("tablemode"),j.addClass("listmode")):(e.cookie("listmode",1,{expires:365}),k.addClass("listmode"),j.removeClass("listmode"),j.addClass("tablemode"))}),1===e.cookie("listmode")&&(k.addClass("listmode"),j.removeClass("listmode"),j.addClass("tablemode")),e("#nav-btn-add-tag").on("click",function(){return e(".nav-panel-add-tag").toggle(100),e(".nav-panel-menu").addClass("hidden"),e("#tag_label").focus(),!1}),e("div").is("#filters")&&(e("#button_filters").show(),e("#clear_form_filters").on("click",function(){return e("#filters input").val(""),e("#filters :checked").removeAttr("checked"),!1})),e("article").length&&!function(){var a=new f.App;a.include(f.ui.main,{element:document.querySelector("article")});var b=JSON.parse(e("#annotationroutes").html());a.include(f.storage.http,b),a.start().then(function(){a.annotations.load({entry:b.entryId})}),e(window).scroll(function(){var a=e(window).scrollTop(),d=e(document).height(),f=a/d,g=Math.round(100*f)/100;(0,c.savePercent)(b.entryId,g)}),(0,c.retrievePercent)(b.entryId),e(window).resize(function(){(0,c.retrievePercent)(b.entryId)})}();var l=window.location.href;l.match("&closewin=true")&&window.close(),e("a.closeMessage").on("click",function(){return e(void 0).parents("div.messages").slideUp(300,function(){e(void 0).remove()}),!1}),e("#search-form").hide(),e("#bagit-form").hide(),e("#filters").hide(),e("#download-form").hide(),e("#search").click(function(){i(),a(),e("#searchfield").focus()}),e(".filter-btn").click(function(){i(),b()}),e(".download-btn").click(function(){i(),g()}),e("#bagit").click(function(){i(),h(),e("#plainurl").focus()}),e("#search-form-close").click(function(){a()}),e("#filter-form-close").click(function(){b()}),e("#download-form-close").click(function(){g()}),e("#bagit-form-close").click(function(){h()});var m=e("#bagit-form-form");m.submit(function(a){e("body").css("cursor","wait"),e("#add-link-result").empty(),e.ajax({type:m.attr("method"),url:m.attr("action"),data:m.serialize(),success:function(){e("#add-link-result").html("Done!"),e("#plainurl").val(""),e("#plainurl").blur(""),e("body").css("cursor","auto")},error:function(){e("#add-link-result").html("Failed!"),e("body").css("cursor","auto")}}),a.preventDefault()}),e('article a[href^="http"]').after(function(){return''}),e(".add-to-wallabag-link-after").click(function(a){(0,d.toggleSaveLinkForm)(e(void 0).attr("href"),a),a.preventDefault()})})}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"../../_global/js/tools":1,"./uiTools":3,annotator:4,jquery:31,"jquery-ui-browserify":29,"jquery.cookie":30}],3:[function(a,b,c){"use strict";function d(a,b){e("#add-link-result").empty();var c=e("#bagit"),d=e("#bagit-form");c.toggleClass("active-current"),0===c.length&&("undefined"!==b&&b?d.css({position:"absolute",top:b.pageY,left:b.pageX-200}):d.css({position:"relative",top:"auto",left:"auto"}));var f=e("#search-form"),g=e("#plainurl");0!==f.length&&(e("#search").removeClass("current"),e("#search-arrow").removeClass("arrow-down"),f.hide()),d.toggle(),e("#content").toggleClass("opacity03"),"undefined"!==a&&a&&g.val(a),g.focus()}Object.defineProperty(c,"__esModule",{value:!0});var e=a("jquery");c.toggleSaveLinkForm=d},{jquery:31}],4:[function(a,b,c){(function(b){"use strict";var d=a("insert-css"),e=a("./css/annotator.css");d(e);var f=a("./src/app"),g=a("./src/util");c.App=f.App,c.authz=a("./src/authz"),c.identity=a("./src/identity"),c.notification=a("./src/notification"),c.storage=a("./src/storage"),c.ui=a("./src/ui"),c.util=g,c.ext={};var h=b.wgxpath;"undefined"!=typeof h&&null!==h&&"function"==typeof h.install&&h.install();var i=b.annotator;c.noConflict=function(){return b.annotator=i,this}}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"./css/annotator.css":5,"./src/app":7,"./src/authz":8,"./src/identity":9,"./src/notification":10,"./src/storage":12,"./src/ui":13,"./src/util":24,"insert-css":27}],5:[function(a,b,c){b.exports='.annotator-filter *,.annotator-notice,.annotator-widget *{font-family:"Helvetica Neue",Arial,Helvetica,sans-serif;font-weight:400;text-align:left;margin:0;padding:0;background:0 0;-webkit-transition:none;-moz-transition:none;-o-transition:none;transition:none;-moz-box-shadow:none;-webkit-box-shadow:none;-o-box-shadow:none;box-shadow:none;color:#909090}.annotator-adder{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJAAAAAwCAYAAAD+WvNWAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2ZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDowMzgwMTE3NDA3MjA2ODExODRCQUU5RDY0RTkyQTJDNiIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDowOUY5RUFERDYwOEIxMUUxOTQ1RDkyQzU2OTNEMDZENCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDowOUY5RUFEQzYwOEIxMUUxOTQ1RDkyQzU2OTNEMDZENCIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IE1hY2ludG9zaCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjA1ODAxMTc0MDcyMDY4MTE5MTA5OUIyNDhFRUQ1QkM4IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjAzODAxMTc0MDcyMDY4MTE4NEJBRTlENjRFOTJBMkM2Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+CtAI3wAAGEBJREFUeNrMnAd8FMe9x3+7d6cuEIgqhCQQ3cI0QQyIblPiENcQ20KiPPzBuLzkYSeOA6Q5zufl896L7cQxOMYRVWAgxjE2YDq2qAIZJJkiUYR6Be5O0p3ubnfezF7R6rS7VxBlkvEdd3s735n57b/M7IojhIDjOKgU9xfchnXrFtPjltE6Gne/CJQrj9bVmQsXrqf/JuzDTRs2EO8D52dmap3Hwz/9+X9K/PTtPeGnyBL/oS2LPfwzXljXjv9g9kK/+H8WNXsxB8aPe8SPPAKy+v3GvR7+n0fNacfPaQiIfch98vHHY/R6/bL+ycmLhg0bhq6xsXednjHdbGhAYWEhbpSUrHU4HKv/48UXz7GvNq5f36YTGQsWaA0+N3XeR2N4Xr8sKTF5Ub9+QxEZ1ZWe/673AM2NN3Hl6vcoKy9ZK4qO1Ue2LZX4Zzyf1ab1g1sWafK/GjVzjA78sjE/GLto8oxpiI/vA4h3EZ22KhIRFRUVOPT1AeTnnVsrQFz9QeM+id9bRHoteFaZeCakpS1KSkqCzWaDyWTCvSjhERFIm5SGuLi4JSeOH2cfveQWjLeItPg5TrcsdczERTFdk2G2AMY61+V0V+eAg8EQi8HDJqNnj95Lcs+28jPBTH/un37z6zh+2U8XpC8aO3QUSIMV4qVbd78DPNAnNAaZz83HqeFDl2zfsMXD/17jHvw8ulVEvBb8P9eulSwPU31jY6MkIFEU70llbZnNjeibkIDExMQljMXNRUUkWU6ibEo4mfVZlpiQvCiyUzLqjYC1hdpmevWKd7myNlhbDbeByM4DEd8ncQljcXMd2kq9kaQCbf7XomctG00tT2rScJByM9BsZ+YBkgm9m1UgUlukzIxx/Udg+KgRSxiLm+s98x5OS0DuTvC0LB0ydAgsFus9E453tVgsSHl4OINZKufVEJCHn+P4pX2TUmBsdgmH3NvqoG2aaNv9B4wEYwmUn7qupdPSJkNssECkkyqK97iyNustmDnjMTAWJb3o1a6AH86ZE0YnLSUsLAxWdjndxxISYmC+KGXkyJGGc+fOsVEXifroS/wJQ2aH8RyfwuliYLfffauvViSrFNaJubWUbnEjDPWV5yV++OBPDekfpjPoUnqEdAFpbrl/HaAiiuWjqZr5lP76HoZrjlonP+ck4tWi/oS+fSN0Oh0dfBsEQbjP1QEai+GRceOi3YwLFy/mFObAwx8VEx9BOw2b/d64LS135hB46PQ69EgY6+E/vO1FjrSPhj383XWdIgwGA4iFuhJ6EiLep0rb5h0EIaEhGGyI8/C/Z3K6MVULZLFaeTZBbldyPwtrn7EwJlmMQLRiIIfdIvELrknUSPnQaCxDk7kqYK4e8WNhs95GSFgMc1GqxzkEp8tiTP7y2+Dg2TspLBGJRr5HUG6uRVVjfcD8qb2GwtjSiM6hUdTf85pWiLFITDJ+9l/VLMxht3NuATEroFbs1D+sWfMRNm3aFHAHvv32Wxw7loNHHnkE4eHhGgLiXRNg52RXqWYMIQr0WJqOSvGIhoCs5nI8MyMUT82cGDD/whWlGJpowaUbTdCH91EVkTT/jEVoy88+U+WHyHkuHo0OlFvqEPHjAZg699mA+Ytf2gnb4EiYixsQZ+iiKiLO1b6LifNK2JSvALsgcCK7gn24l3/84x9BiefGjRJs3LgRK1asxOrVa6RgWasdxsKYZFeA9JkaPxGd/CwYFDTqE9OYePoEzL/490Y8Ng54Y8kgPEnPYWmsoJZGUGxDCkhZ0Cy25deyQAKI8xiRaNbIHw5AwtyRAfPXvrYP+mnxGPafjyLy8WRUWm7ScRZV23GuLpI2/FoWCILD4UmVtVzY7t17pNedOz/DuHHj/IvL6EAfPXpUEhB7/+mnn0qB8qJFi+hriOLCouSOKJP35+pWi/GLPl3Y9PHdpdd3PmlBcTnve4lQFKglNCIxrjOendMXOp7DE4/GweaowFfHacqli2rfX5GxihJTW351MHa1Ow2XtgXqOWWQ9Gr6v1zgutmPmFiEyd6Mzgnd0O3JUeBonNj38REotYtoPlCFSBKmmAmQVgskc5/tBcTJV6iJy31pubCWFmeGFh0djStXrvjsALM0Z86cxejRo/CHP/web7/9R2lx8rPPdkquLCUlRVFwRPQkLq2MYrvggGt9lYIHnwIKMThFc6OaaMdK7gl31GFIvAVXK5uwcXc8np+lR2Q4jx9N642L5QKKy6AoIKe7asuvENxwbV453y6MD3FOob3CBJ2onaoxK9hAzLAODEfj9Urot11GxDODwEcYED87BY1XHBCvGZVdGKfASHug17ASflkguZBY1qZVrFYrvvzyK8nlTZkyBa+/vhy/+tWbePfd95CZmYGHH34YDodD3QI5XZh/FsjFL/oKomWT7PM4Wx2mjgGef3wAvsmtxebd5eD5BDwzHdh/muBqhfI5RNHJKgbA73FhgjMT8mkZaaDr67gGwQw+rTeGPTsG1ceKUbK9EP2oBQ2bmwzb0TII143KHXB95mbyZyvD2WFpArQtkDxT8nXcnj17sGvXLixYkIkPP1xNU3Mdli9fjuTkZAwYMAC3b99WHFTGICosvImam1rE6TZ8BNHyeFbrOIu5ErPH6yRL8+XRevxkVk8a89Rg2yEzymujcfmGugVzLh6L7VaetVxY674U0czCWseIJkUax1U1NSB8eiL6zh6Oqq8voM+TI0AcIhq+uIqYqibYi2+5on0FDEK8QudWPrUgGm4X5lyVVF8plgtIq2ZnZ2P//gOSeE6ePCVZmiNHjiI3Nxfx8fG4efOmM1hW/D2Ru7BWRuUZ59yTI0/j1ao8U1U7pslUhSemGvBYWg98cZi6sKQQ6HUcpozrjv4JUSi4SlBbcU6zHacVFdsxauzAA7IYSK16RKlxTDVN8aNooBw3Yygq9hQifGA3KfbpNWkQovt1h+1iPfJriny0o8zIq1+/8Fz1WtXbzSjV7du34/jxE3j66aewb99+nD59GrGxsTRoXojhw4dL+2zp6fM1zyGxKPh0TQskiU97oU82/u0XAanIm6l45k7SYcrYbjhwvAGpw8IxalgMjI0C9p6gqXBJC+rLT2Hz/4zQbKfNZPtjgVy5DnNNoiCq1lb+9t/ZHHZpfSh8Vj/0nDAQ1UcuI3pkHGIf7guHyQrrgRtoLq5DbvUFjP94gWobxLUO1M4KcRoCgmfyxKAtkNlspsHxZzTj+gZPPfWkZHFOnTqFLl26UMGkY968eaiqqsKsWbOllWa1NtzWxPs+DK0YQmKH6HO/Su5m2uxjOWzgHJX40eQQzJjQHfuP12Hk4DCkpsTA1CTi65PAvw6LiIrkcHhjmuI55JUo7F74dGF+WSDl42yUv1q8jaiZyeg9dQgqD19EVEpPdBuVCMHcAuvhUjR/eQVcpAFzvnrdZ1tqRTsGoj9soYGvpbnZZ0dZgCyf4Pr6euz8/HNqXZowZ/ZsfL7zc1y8dAnstpDXXnuNZlw/QGVFRZugWa0dGip5VqO94y5Nfnr11Jpo8GjSWsl1lhp6TKOVuAbSjq5htUif2wU9YsPw9bEGTBnTGQ8NiEJZjQPrdhPsO0Ngp+gtQqsLrDIqt2Ojsad0JXsLyEdwxgRWe+EaBKNV9Ziu4mPSa92F60Cj3bnyTQSYYoGkF9MQ2SMGJbvOoMe0oYhN6QtL6U3UrT0N417qsuwUvmcE4thYOgTUFChn0brOYcpi11oHct9swG4207hjsa3FdR1369YtfPXVbjQ3NUuZ1cFDhyTxJCQk4KWXlmLUyBGoq61t5/DV2mGfK938QHy4MCkyVr1rQrnDRHSgU0gd5s+JQq9uYSgsNmHiyChJPBV1AtbvEbAvl6bN7iUdoqBGxXO3d2Hww4VxAtsW8OMeJHaMw7XO04Wgb+Z4RPXsgvqCUnSnsQ4Tj7X8Nmo/zoVp92WqatE59kIro1o7jCFgF+bLdKkVFs/s+vJLlNy4IYnn22+/ke4s7NOnjySeQYMG4ZZKtuWPKffXAkliCOLWwwjDbaTPMmBY/3DkF93EhBERGDE4GtUNIjbsJTh9kW2rcAGf1+mCA7kAPHsamtX7uKYIET0XpCImJR4150rQLW0AdVtJaKkyoeHjM7AeKwXv0D6HVjv+uzB3Bzn4Z4FcluokjXHYWk9cXG/s2LEDVdXVGDhwIN5++w/oS7Mto9Eo7Z+5B09+btV2OHdM4/8EEFcaH5gBIpg+miD98ThU1bXg6RndEdc9FNcrBfx5sw3fFet8nkN9LEUQBB4D+ZrA1lTbue3RaeZADF4wGU0Vt5A0bywi+3SF5WoDKn53AC1nKtunUV4CUmNQmxefMZBLQX70gJOyory87ySBlJdXSGk5i3lWrPg1uyEMdfX1bY5v8+r93os00BgIUuAtBGQlOGLDlNERMOg59OkRCh1N1ctqBLy7TURZnR53clOOxOIlGE0+uQvzoxvsGAc9f4/pg8EbdIiK7wpOz8N64xZq3zkC8bpJ+Tyil6sK0IXpfWVhfsdA9Bi2lsPclfvfDz30EJYv/y/JfTFRsaq17KEZAwWahYH4dYXLS2xUE0YN6e7hKioTseZzEXlFzoD5TkqwFogXtUMl+XH2biHolprkGVbrhVrUvXsc1hMVUsDMqyygus0kL6qfO+gsTEl4ahdMYUEhevXqheeeew5paRMl12W1WNDU1OQUo49VM07j3IFbIBJQDCTYTJgwPgb1Rg67jjtw5hLB5VKaEJi19sjYBi/bwIz0MwYKfCWaJ/4JqEmwonfacIg1zbi54wKaj5XB9n0thAYLtSCi4tgyQVscLZ4xVhUQgepKtM8YyJcFiomJkdZ7mOtiT1E8/czTUlvSExw03nGn6UrnYC7ufP556X337t19WqCAYiDXSrqvYmwiiIoAUgfcwjfHS3Ekh8DcJMBqE6jV0RYgc3EjU3rQd73QYPQjCQgkjWdxHxOQQPsuqI+/eIum+NFhcIzvgfzDuSAHTsFuskCw2CHatX0fc3GJ41Kdc1HXLLWlKCDGoGBJiIqASBsL5ENAmZmZeOedd/Dff/7zHZn4n86bpykgLwtENCwQke+F+So7jnD42U+A/31jyB3x//sYD60Htrz2woiGBSJtLBC7g0JUH/+mdQUI/c0k/OCjzDvit26+AJ1KOxIDp8DoTwwEHwJ64okfIzw8DCtXrgoYmu3es62M+fPTkTZxIhoaGjouBnKtRPsq2fsFKb5543ldwPxMvxdvEHz+rYAvckSt/CLolWieXeYah5k/yqPmXkDXP04NXDUCQUtBDRo3FaJpy/eqazq8xrKFqoAKCgsbJ0+Zwp6NkTIotcmqr6vDzMcek24GC2ZthN0fxITDnkRVEqr0Gf2/xWq1HTh40OjvXtjt2kuNvRIfgY46dl7KENU5th8WpHo3Cs+sCC/QGKvZVn09x+jvQmKRtapxnDAAOnbbjchpJoDNa/OleidFB/UlFFZaHDbbCXOR0VcM5MYkNTU1gt1mO2M0GVNDQyNosKg+wEwAatbD7xRaxcqxpxnY2pHDbv/Om1EhhvB8Z22qpyFWyxnOXpaq1ydIT2fcj6KnI8y1lFFrpcBP1Pkb7GbBQYQz1Tpzam9dGIhNuC/8XIgOFbwZAsR2/NqbqfQAk9mclZd3nrqoUPDU3XDUEt3LysQTFhaKgoILMJpMWd4LMdq78TRzbWnMaijZg+hwZkXv/eDraJus7VtlB2Gzmtvx+3BhpFlsyfrG+j30ESHQcbwUo9zTSttkbZ+0XUYTZWm3EKYiIPfiLXn//fe3FhUVbygs/B6RkWEwGPSSO3MH1nersjZYW0y4hYUFuHDh4oa//vWv2+VsGjGQ55hLp7O23qou2GCv34Ou0RxCDezc7pju7lQnP4ewEA5dogjsdV+hoTJvw+XcdQr8oiZ/VtWRrRcbSzccNRRB3ykMOjb+7H90cu9qZWKlbek6heKw/jIKzNc3rKs60p5fIwYirpRCzMnJ+RO7FbO8rCxjzJjR6BzTBexpVfcEOhyilKqLYnCrtGyw2Z2JrLrdGHuU2nj7JnLPnMX1ayXrjxw9+o6bp00qI4rwxV9XdvZP9ECuU31RRvd+M4GweBBdJ9c9RtS322gGYvPvtlc1KxMWAoSGOOMdqQ+CEZytAnUX98JYf3l9bekpRX6NPxPi4T9jvvYnGsNy10NrMqbEPoQ4eydECqHO37IO2GhwbnU4bwcIqgP05KFUBqG81AGOVhPfgmqDCUeshSg2V64/aSxS5tdI491VOHHiRD2tby7IzDxcUlKaodfrh1ML0c198JChgzFhwgTYaJARqIiYeEJDDcg9nYv8/EL5AmENFeWF2trajes3bNjLlpXg3DcOyAKx39RX5NXT+ma/4U8dNtVfzuB43XCOa+WP7TMWnfu+AGMTH7CImHg6RVIRVm5HWWmO3DXVEFG4YG1u2Hi9YKcGv+iTP890rZ7WN5/t9cjhq7aqDD3lpz7Awz8quj+e0o8CZ3Y4H8YPVDyRIdgVWYBTlstOQkF67rrGYREu0Dhs447qk6r8akE054Z3vWcrgbxrIg9KAbuzMvfHv/rqqyx/f2EiTcMDEZFbPKdOncaxYye2/u1vf/u9TOWCq115FWSdwFtvvUUUYiBVftdEtuMfOMa8qhchL3ROSA9IRG7xWCu3oap479ais5sC4h82fqlaEK3I75rIdvwL46etQiT3wjNigCJyieffEfk42JS/NavsUED8rybNIWouzG0+OVknIDt5mw588MEHv6WnY4/ppk+aNMkvETHxsOfATp48ycSzhZ7jNzJwUQbr3QE3m8bfVgiMv/jspt+yxzd6gqR3Tpjvl4g84qn4FFVX9m4pOrs5YH6NFD4g/nXlh3/LJXCEi+TSf+KviFzi2RlNxdNcsIWKJ3B+V7jhKwaC68dEdmJe1gGpM1QAq1555RV2zPzJkydrisgtHuoWmXiy6W9XymAFlY4I3j7Yxz5XQPxFeZtXsYioJxHnd07M1BRRq3i2orJ4b3ZxXnaQ/GKH8WeVHlqFRI4gGvN/SkaDM2mIiIknKgSfdTqPg5b87KzSg0Hxu2WtZoG4Nmpr3wFe1gF2DvHvf/87BXmFWYaMqVOmKIqIBWihVDzHqXhyco5n09+soB/bvVQuqlSP7/3lL3/pywIFzF+ct2WlcwsfGZ2TlEXkEU/5Fqd4vtsSFP/QcYsJOpg/6wYVQhIVUScu4zlxNHglEVHxgIrnX53PY39LQTb9TVD8ryQ/7qHXskDenZGbVvdfadDJG6WCWEXIy2xsMqZNYyJqzc5YdsJinmPHjkni+fDDD3/tgpd3QAm4DfwvfvEL4scue1D8VBDMEqEXCBXRgjYicovHUp5NxbMn+8p3nwbFP2TcQuLHFktQ/FklB1ZREYGLQcbzxEtETDzRIdjRJd8pnpIDQfG/kvwjv/5GohK8fFPf3Yl26qTCWEkI+2tohIpoGux2h3SxMfHk5OTIxWPz6oCgkCq2uaHwjTfeIAHcohEUPxXGShaf9IJIRbRIEhErTvFsRmURFc+5bUHxDxmbSeD/PUpB8WeV7F9J+nEgXbiMdLclYmNGLc+2rvnYZyvIXleyPyj+lwfMbTf6ej+vBO9/K5lYT2OrV69e6XwkCBmPPjpDsj7s0Z6cnGOb6Xdu5du84NunibS8/vrrxJ/N047kv3Juu8Tfi/J3TV4srdk33tjELM9m+l1A/INTM+45/7rr+1aiPz0olsuYz4+RNkM/7XoO++35m+l3AfG/PHCuJrQ+yM4QtL3JsV1H16xZs4IKh32eyf7ihks8b8lUr2Q6iVwwHVwC4r96fgfll1brMnX6MCqe3VQ8//LJPzg13etc4n3hX3dt3woumY5/F2SGwoB9joLNWdf2+eR/edCPAxp/fQd0SJ4ttFkMY4KxWCx5Op0u4pNPPlkvi/YV4ZcvX04IuWd/DNAnPxOMYG/J4zg+4lrhFz75B495geAB4s+6+vVbln72PB3l33ztgE/+ZYOfCJie8/GX6v06h8wnyzMDveu9/CqRp4vtxBNM43/5y1/ueMO5I/gl8QRRLp/NfiD4mXiC2oq6U3rXxBOFVUzmY1tcr/Lq6CjxdERxTfwd8Qcrno4orom/I/5gxdMhAlIQkXwF064CLzwI4lERUUD891M8KiIKiP9OxNNhAvISEVFZDpevaJIHRTwKIvKb/0EQj4KI/Oa/U/F0qIA03JnS+wdKPD7cmSL/gyQeH+5Mkb8jxHOnWZiWiOTBLVH6/kEtbmHIglui9P2DWtzCWH3534r8HSUcd/l/AQYA7PGYKl3+RK0AAAAASUVORK5CYII=);background-repeat:no-repeat}.annotator-editor a:after,.annotator-filter .annotator-filter-navigation button:after,.annotator-filter .annotator-filter-property .annotator-filter-clear,.annotator-resize,.annotator-viewer .annotator-controls a,.annotator-viewer .annotator-controls button,.annotator-widget:after{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAEiCAYAAAD0w4JOAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBNYWNpbnRvc2giIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6RDY0MTMzNTM2QUQzMTFFMUE2REJERDgwQTM3Njg5NTUiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6RDY0MTMzNTQ2QUQzMTFFMUE2REJERDgwQTM3Njg5NTUiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo2ODkwQjlFQzZBRDExMUUxQTZEQkREODBBMzc2ODk1NSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpENjQxMzM1MjZBRDMxMUUxQTZEQkREODBBMzc2ODk1NSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PkijPpwAABBRSURBVHja7JsJVBRXFoarq5tNQZZWo6BxTRQXNOooxhWQBLcYlwRkMirmOKMnmVFHUcYdDUp0Yo5OopM4cQM1TlyjUSFGwIUWFQUjatxNQEFEFtnX+W/7Sovqqt7w5EwMdc6ltldf3/fevffderxSZWVlZbi5uTXh6rAVFBTkqbVubl07eno2d3BwaGgtZNPGjYf5wsLCDRu/+ir20aNH2dZCcnNzN6uPHTv2S2xsbHZaWpqLJZqJIR9FRMTxdHFJeHiiJZrl5+fniiF0jRdumgsjyOZNm44AshHPxAnXeXEhUzAJJEF8j5cWVoIZg9CmqqiokK3CksWLX3d0dJwy+f3331Cr1RoliEajMQ4Sw2xsbHglTZ6CampquOex8dxz2l5gkEY4qKyslOu1Qa6urpPRs9VkW2RjFmskQCaFhASQLZEZkDlYBBJDnJ2dXSnwmYLxpiDCdVMw3hyIObCnlr1g/nwfQCYpQcQbOTM5tbgDeDEkZPLkoaYgSpqpKysqnkIaNWrkYq7dUEim0EwhmkI1bw1ETjNVTk7OA2sg0jarDyO/ZhiJjtpS4923L1dWVs5VV1vW8Dyv4uzsbLnkc+c4dceOnn1LS0vat23bhnvSgypOpTItajXP2dvbcefOneVSL146ys+dOzvgyuWrMadOJeKGrb6AeRBb7syZM1xqyo9HwfDncZ0L+0dowGXATpw4qVfVGEyAJCUBkvrjUTzrTwzUkirDcfOewk5w9oBp8AD9iljoGt07rTvNpaRcPDqPIOx5+mlOkPnz5wakpV2JiU84ztlRNTVqTsXzeuHValyz4xJ1Ou4CICjrL37WoPsXLAgD7HJMXFw8Z2ur4dT8E23s7Wy4UydPchcupB5FGX8ZOxKUeyYLF84LSLt0OebYsXi9ZvYOdtwJBsE9f7lnVAUFuYp2smxpxJFOnTu9aWtry6VcSDm6cNF8f6WyRkEMFg7rclq0aP7fjZWrDyNmeL9c8iDedu7YMRK7xoHjx28y2tjGcsivt29PaOTsPNAGeSIGidNBwcF9La6aAPH18+UG+QzmtFqtN67pLALt2LYtAUOUHoLMWO/1BMM45o17OgUQ2dEz2R4drYf4AMLzakTNahY5n8FQRid9rpZG26KiE5ypOkP89JqIjZWOVSqeG+zrw7lp3bxRVidbteitUQnOLtQmhhApzMfXFzCtN57R1QJFbdkKiMtAP0Ao7lB16CE5oXtUTYJRB+BZPUzd6uWXE1xcXQcO8R+iqIms3aADWrdpw2VmZrbQJeoCeBdoYinkWTVVHNVC21jrrSopKakh67Y2ChCMXmw0xizbXM2I8dyc9gUObBpTBTw8WqixGw45n5GRnl4XjaZD9kP+DaibVSA8OAu7SHZKWm3GtTYWgfDATOxWQGxElynsepkNAoSq808JhII7DZKHzWpsQGYwiPhHyPzD0NifmtVGrE1WUlSQaDIXkNVm2REgc1jDiqtTBQk1pkmtqgEyCLu/SqpKkFmArDHLsgGxw57euaiXIkSQOeZCBI1egtCs324IxVGy3s9NtYkcqCtkGBtXHkLeAyTBGl8rZPZxCfIAkNIXLB6h9/4A6a/gMv0hvUyCUKgLdlsoXODYXwJ5E7sDzPM7G7OjPtjvgnjSizNkqwDDPoD9AL08E2QXaa7Ua40gLUTXmkHW44Gd2I9ndiZsLVh52ar9AAlmNiRs7eg9ByIOYtkMHGe0+6HBW9ithbSSKXcH8iFs7DuTvYZC31KKpFAuyhhE2v3kJkEK5YJZwytbtru7B8GGQjZCmhopmwkJgcRCu2o5jXwh2yWQWyxS3pH05teQwUpVK4Jkia49YA07l/ast8T3ihR7DfXvhuP/Mq2CATksarsRrBPuQQJx76Kp7vfGzh4F42V8zQe7YtxL+u2EkVoDZJ8+fej8VQi9vPRmg8BpCKXAN5OSkqpNVg0QR7VaPR3n05FLN6k9mcJnYLcK178ErEQRBIgTMtMNyG4Djaqv0XyJMtMBM4jrPCC8vb19KEHatWtXMHbs2LtOTk7lQoHGjRuXjBs37q6Hh0cRyvwZr+5/kW1s3GhXVVWlfxXv27fvhTlz5iybNm1aCuBVeEsqnzFjRmJoaOjS7t27X2fVXIgfdzfQtnnz5sPv3r2r/3/Rvn37WkdHR/8I1UNdXV1X4kdK+vfvPxsPNm3YsKE++JWWlmpbtNBH0C21QDY2NgOEk8LCwlY4340HhwM2DZfKcaxFJ+wsKip6OlfZoEGDwVIQD/Vrzc1Ciyb+/v4UGS9A0nx8fDxRHSdxGbzTaQ2q1qpVq3vnz58XGrYUbZIM0FVo0gOXyqBZ8p49ey6tW7fO8/Hjx7ZUrm3btgbZLe/p6Xnczs6ODI8bMWJEGiDTAfGAFjGo5nc4rh4zZswMaKYPKdSjXl5e8XLdfzQgIEBf6ODBg2qcv47qRcH4GuNlpRWOd+Bap8TERH0CNnz48Gv9+vVLkDNINXrtg8jIyEWootaYQaIHs2AKc5s1a7aVZS8GLuJ0//798M2bN4+NiYlxxztcLR90dHSsGDlyZHpwcHBU06ZNKWUuNRZGnGAjwTdu3BifkpLS7PLly05oJ65r164FMMZ0WH0UXIRG5GJz4pGajaad2RBOnXCZSYa0OrVAMueOEFc23tODuUyKxSBpQBS3hcbd3b396NGj+/v6+np16NDhVfRcNar40/fff5+ya9euk/n5+XeYlsoRomfPnv3j4+O3oJ0e1Ug2uMeDQ4cOfdmlS5deQlSVzgfoqzNkyJDXrl+/Hl9jYrt48eIh/GBHWRCq4HTq1KmtVLC4uDgZu48QVrKFhxGD7mC3DCZxjc5jY2M/o9HGAAQfGlBeXv6YCqEtKLd2weFYNM9jALNwTJ7e5OzZs1Hsx7JXrlzZ3QCk0+nmCb+el5d3Jzw8/ANKpnDqC6FBQLt27dp5CDGZQrnjx49/aACCe2yRNOx9wPsJvQBN3iorK8sXl7l58+bnUpDGwcGh1lQEQqyNt7d3GYUdeqXo1atXKQraissgWlbIDAyaZOzfZ/8+TMd5iEqluhMWFvZHmEIpjncDNAHttR6RUsuC31kDA4LanihUxOq+ivLGNWvWzAYjF4Hs3qJFi6bgWuvU1NStrBepR1satBH+0ERLJBXKyMi4AMP7Ag2bJbRHbm7unQMHDqzPzs7+ic5RNgw7lZxB0oErfumgKYOE5tHYNVSybAHmBlkB+8mXAnDtISALcdhI7LRiUUnmgowmEWj4akXvF1+g4Zs6hYmGRUIyhXLKRIzlUuJshEYOyvZDUBUHaTaCax/jcINcAiHORlpi6NmJHulrIhtZi06ZDViF3HAE43aINAahZAIWD0bl3wD7E55RGYBcXFy84f3vKkFo9IWVJ82aNSsVY34lNF8Ky25pAELW8Ta6VnZCSqvV0hB+ys/Pb/qZM2d2oRxlI+4Y194wAKFLe9IBDduBgYG3e/TooX/dwg+UzZw5U4chnNKatgjDoXAnDc07oikGGrQf1G1AB+3bt8/FABgJ1duvWrXqvUGDBl0HZBYgbSgtRBu6irIRZwONkDTRywqH0UL7zjvvvILBMQLD9+qhQ4cS5GVAvkIju4pMoQY/+osBCDFbh8arIkdEo89euHDhAgC+ZZpsFEP0bzbNmhUhG/nBADRgwIADqEbG0ymaqqrZqN5+xJ5NgBhMzmHcO4cU57gBqGXLlmkTJ07c0K1bt0dPp68qKjoCaLAOibJbZL00o5Oj5CKu6enpS5CIvo3hpjnito2kOsVBQUE/jxo16hP0zUY2q6OYRDijjQJv3boViDzJHdGyCaUz6Lnszp07X0GnbGRv5JXmZCPk/ZRD08wE2UoBez2/xhIJztxshGfZiBsbRSgePWKQEuk8tlI2Yo8M1xOJZz9kI52QWL2CqpYg6F9FHE/duXMnrX24K9c+4s0B7jEKxngQXV6ikI18gQy4h7FsRD116tQ3MzMzL5kK/uiEfTDgNrIgdKv7lStXYk2MHlmIkAV0jKHpYyRkDQxAyOqDULDMCITSGh/kRpMoa8GWsXr16l5SEA8H7AdHtJVrOGjxC+5NQui4mpyc3Ap7Ncb95sgHDGe+7t279x0biovhGovx8H6mSQZpQoYdFRW1VEgJcb/q9u3b6wyq9vDhwz1suD6PzL4nUhZnnG6AUBRshiQ+HJA80WBZmZWV9YkBKCcnZxErUI3R4Ru4Ak1wksO6b9q0abEYwjQtR0IWaABCKvc6bhYLBRGbd+NV9D1UJ4IyEmnjI9ymYecul43YoTfWiwtTBoJrRXK9iLYMUkwicPASChwxIxtZRm9TprKRxpDlaKocmWzkKnYTITbmZiNqNuNH89tjWSSk6aBk2FCWMe9/kf+7vnz5ilp1k55b8q+/moiI5TWiHpCemyVKD1sM44w8bDXI6mrJgercRnWGGbPsGpkB1CqDVP3GXeR3CLI4CsgZFzPGOvmaVRADkLWQWiApxKp4pACxDPQ8IIL3S728xlKHFexIVRevr3faFwZkdQIhE0ZeoJFWLh5ZBTOlidkwc6plFkwpibA4tPAW/FOh3tfqQRaBrHrRMZWNmDvyPheIrPdbmwO8wBmbNB5ZldLI2ZGq3td+RRBNz0NWWr2ShRaguLi4LFOr1R9UVVXdx6U5FoP8/Pym2dvbr8jLy3O2em1NUFDQ4cLCwoA6t9G2bdscpk6des3BwaGyTiC0yachISHX9+zZk4Qq3qtrxuYEmQWJO3v2bEzv3r2/qWui1R6y5Hl4f72vWTgjY0n78UoDZp2rplKpHCCd6gIiB+44evTod1NSUhZb21Yvd+jQYZROp9tZWVlZVlxcnKU03aFo2di8du/evVa88MQqEP58IZ0Itxakhkyj1R51AkkWDui1QzXvWw0SAWmVyjeWguq9vx70XCIkxjD6T3E4ZGlSUlK+1Rrt3buXFpPSmtFbyEimQdRWgRo0aPA2O6b/X6+DXAQs4Hm0EYXZw4CF1Qnk5uZWGhgY+CnaK9KqjM3W1rZ62LBhVydMmDDdw8PjqMWNlJubewL5UWZiYmIo/WPTmgRCiJBLIc2tBdTHo/+3tMaS1IZnRknLX23qpNLBgwddk5OT93p5edG/nFtLtTTbIOPi4uif4TXl5eUFBw4cWOfo6EgfWTS1GiRa7vnzmjVrKD9qXyeQaAuzBCS37OxnyAykf3utCiPck9U8tEIzEpASa15qaHkHLfloY860UL3314Pk4pG7u4ex+7QYhT60bA6Jh2yAlGZkpBu1bOlGn6HtF52P4Z587duVk6xpM1a1cSLIEchJkYazzG0jWuxOCTstfKMv6OhLMlquF8vuDzcH1I5BaKO1o/tEk3jC0sUcUyD69RvckwWDHIuStIDSHjKE3actwlgYoRXj/2HH9GYkfGlInyreEZ3/jXuyoFlWIy8RRBgAxJ+WCRD6cPdfxgzyI3ZMHwPu4Z6sgKaPLO+z6ze5J0usPzMVIYWPKZ0YuJr1lPB91ihImjmhlj5bfI118SlIHkRIRqeYAxFchNZiX+EMP6ScImq7WpuSi5SwTHYyc4u7rFEvWuS09TH79wz6nwADANCoQA3w0fcjAAAAAElFTkSuQmCC);background-repeat:no-repeat}.annotator-hl{background:#FFFF0A;background:rgba(255,255,10,.3);-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorstr=#4DFFFF0A, endColorstr=#4DFFFF0A)"}.annotator-hl-temporary{background:#007CFF;background:rgba(0,124,255,.3);-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorstr=#4D007CFF, endColorstr=#4D007CFF)"}.annotator-wrapper{position:relative}.annotator-adder,.annotator-notice,.annotator-outer{z-index:1020}.annotator-adder,.annotator-notice,.annotator-outer,.annotator-widget{position:absolute;font-size:10px;line-height:1}.annotator-hide{display:none;visibility:hidden}.annotator-adder{margin-top:-48px;margin-left:-24px;width:48px;height:48px;background-position:left top}.annotator-adder:hover{background-position:center top}.annotator-adder:active{background-position:center right}.annotator-adder button{display:block;width:36px;height:41px;margin:0 auto;border:none;background:0 0;text-indent:-999em;cursor:pointer}.annotator-outer{width:0;height:0}.annotator-widget{margin:0;padding:0;bottom:15px;left:-18px;min-width:265px;background-color:#FBFBFB;background-color:rgba(251,251,251,.98);border:1px solid #7A7A7A;border:1px solid rgba(122,122,122,.6);-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;-webkit-box-shadow:0 5px 15px rgba(0,0,0,.2);-moz-box-shadow:0 5px 15px rgba(0,0,0,.2);-o-box-shadow:0 5px 15px rgba(0,0,0,.2);box-shadow:0 5px 15px rgba(0,0,0,.2)}.annotator-invert-x .annotator-widget{left:auto;right:-18px}.annotator-invert-y .annotator-widget{bottom:auto;top:8px}.annotator-widget strong{font-weight:700}.annotator-widget .annotator-item,.annotator-widget .annotator-listing{padding:0;margin:0;list-style:none}.annotator-widget:after{content:"";display:block;width:18px;height:10px;background-position:0 0;position:absolute;bottom:-10px;left:8px}.annotator-invert-x .annotator-widget:after{left:auto;right:8px}.annotator-invert-y .annotator-widget:after{background-position:0 -15px;bottom:auto;top:-9px}.annotator-editor .annotator-item input,.annotator-editor .annotator-item textarea,.annotator-widget .annotator-item{position:relative;font-size:12px}.annotator-viewer .annotator-item{border-top:2px solid #7A7A7A;border-top:2px solid rgba(122,122,122,.2)}.annotator-widget .annotator-item:first-child{border-top:none}.annotator-editor .annotator-item,.annotator-viewer div{border-top:1px solid #858585;border-top:1px solid rgba(133,133,133,.11)}.annotator-viewer div{padding:6px}.annotator-viewer .annotator-item ol,.annotator-viewer .annotator-item ul{padding:4px 16px}.annotator-editor .annotator-item:first-child textarea,.annotator-viewer div:first-of-type{padding-top:12px;padding-bottom:12px;color:#3c3c3c;font-size:13px;font-style:italic;line-height:1.3;border-top:none}.annotator-viewer .annotator-controls{position:relative;top:5px;right:5px;padding-left:5px;opacity:0;-webkit-transition:opacity .2s ease-in;-moz-transition:opacity .2s ease-in;-o-transition:opacity .2s ease-in;transition:opacity .2s ease-in;float:right}.annotator-viewer li .annotator-controls.annotator-visible,.annotator-viewer li:hover .annotator-controls{opacity:1}.annotator-viewer .annotator-controls a,.annotator-viewer .annotator-controls button{cursor:pointer;display:inline-block;width:13px;height:13px;margin-left:2px;border:none;opacity:.2;text-indent:-900em;background-color:transparent;outline:0}.annotator-viewer .annotator-controls a:focus,.annotator-viewer .annotator-controls a:hover,.annotator-viewer .annotator-controls button:focus,.annotator-viewer .annotator-controls button:hover{opacity:.9}.annotator-viewer .annotator-controls a:active,.annotator-viewer .annotator-controls button:active{opacity:1}.annotator-viewer .annotator-controls button[disabled]{display:none}.annotator-viewer .annotator-controls .annotator-edit{background-position:0 -60px}.annotator-viewer .annotator-controls .annotator-delete{background-position:0 -75px}.annotator-viewer .annotator-controls .annotator-link{background-position:0 -270px}.annotator-editor .annotator-item{position:relative}.annotator-editor .annotator-item label{top:0;display:inline;cursor:pointer;font-size:12px}.annotator-editor .annotator-item input,.annotator-editor .annotator-item textarea{display:block;min-width:100%;padding:10px 8px;border:none;margin:0;color:#3c3c3c;background:0 0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;resize:none}.annotator-editor .annotator-item textarea::-webkit-scrollbar{height:8px;width:8px}.annotator-editor .annotator-item textarea::-webkit-scrollbar-track-piece{margin:13px 0 3px;background-color:#e5e5e5;-webkit-border-radius:4px}.annotator-editor .annotator-item textarea::-webkit-scrollbar-thumb:vertical{height:25px;background-color:#ccc;-webkit-border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.1)}.annotator-editor .annotator-item textarea::-webkit-scrollbar-thumb:horizontal{width:25px;background-color:#ccc;-webkit-border-radius:4px}.annotator-editor .annotator-item:first-child textarea{min-height:5.5em;-webkit-border-radius:5px 5px 0 0;-moz-border-radius:5px 5px 0 0;-o-border-radius:5px 5px 0 0;border-radius:5px 5px 0 0}.annotator-editor .annotator-item input:focus,.annotator-editor .annotator-item textarea:focus{background-color:#f3f3f3;outline:0}.annotator-editor .annotator-item input[type=checkbox],.annotator-editor .annotator-item input[type=radio]{width:auto;min-width:0;padding:0;display:inline;margin:0 4px 0 0;cursor:pointer}.annotator-editor .annotator-checkbox{padding:8px 6px}.annotator-editor .annotator-controls,.annotator-filter,.annotator-filter .annotator-filter-navigation button{text-align:right;padding:3px;border-top:1px solid #d4d4d4;background-color:#d4d4d4;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),color-stop(.6,#dcdcdc),to(#d2d2d2));background-image:-moz-linear-gradient(to bottom,#f5f5f5,#dcdcdc 60%,#d2d2d2);background-image:-webkit-linear-gradient(to bottom,#f5f5f5,#dcdcdc 60%,#d2d2d2);background-image:linear-gradient(to bottom,#f5f5f5,#dcdcdc 60%,#d2d2d2);-webkit-box-shadow:inset 1px 0 0 rgba(255,255,255,.7),inset -1px 0 0 rgba(255,255,255,.7),inset 0 1px 0 rgba(255,255,255,.7);-moz-box-shadow:inset 1px 0 0 rgba(255,255,255,.7),inset -1px 0 0 rgba(255,255,255,.7),inset 0 1px 0 rgba(255,255,255,.7);-o-box-shadow:inset 1px 0 0 rgba(255,255,255,.7),inset -1px 0 0 rgba(255,255,255,.7),inset 0 1px 0 rgba(255,255,255,.7);box-shadow:inset 1px 0 0 rgba(255,255,255,.7),inset -1px 0 0 rgba(255,255,255,.7),inset 0 1px 0 rgba(255,255,255,.7);-webkit-border-radius:0 0 5px 5px;-moz-border-radius:0 0 5px 5px;-o-border-radius:0 0 5px 5px;border-radius:0 0 5px 5px}.annotator-editor.annotator-invert-y .annotator-controls{border-top:none;border-bottom:1px solid #b4b4b4;-webkit-border-radius:5px 5px 0 0;-moz-border-radius:5px 5px 0 0;-o-border-radius:5px 5px 0 0;border-radius:5px 5px 0 0}.annotator-editor a,.annotator-filter .annotator-filter-property label{position:relative;display:inline-block;padding:0 6px 0 22px;color:#363636;text-shadow:0 1px 0 rgba(255,255,255,.75);text-decoration:none;line-height:24px;font-size:12px;font-weight:700;border:1px solid #a2a2a2;background-color:#d4d4d4;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),color-stop(.5,#d2d2d2),color-stop(.5,#bebebe),to(#d2d2d2));background-image:-moz-linear-gradient(to bottom,#f5f5f5,#d2d2d2 50%,#bebebe 50%,#d2d2d2);background-image:-webkit-linear-gradient(to bottom,#f5f5f5,#d2d2d2 50%,#bebebe 50%,#d2d2d2);background-image:linear-gradient(to bottom,#f5f5f5,#d2d2d2 50%,#bebebe 50%,#d2d2d2);-webkit-box-shadow:inset 0 0 5px rgba(255,255,255,.2),inset 0 0 1px rgba(255,255,255,.8);-moz-box-shadow:inset 0 0 5px rgba(255,255,255,.2),inset 0 0 1px rgba(255,255,255,.8);-o-box-shadow:inset 0 0 5px rgba(255,255,255,.2),inset 0 0 1px rgba(255,255,255,.8);box-shadow:inset 0 0 5px rgba(255,255,255,.2),inset 0 0 1px rgba(255,255,255,.8);-webkit-border-radius:5px;-moz-border-radius:5px;-o-border-radius:5px;border-radius:5px}.annotator-editor a:after{position:absolute;top:50%;left:5px;display:block;content:"";width:15px;height:15px;margin-top:-7px;background-position:0 -90px}.annotator-editor a.annotator-focus,.annotator-editor a:focus,.annotator-editor a:hover,.annotator-filter .annotator-filter-active label,.annotator-filter .annotator-filter-navigation button:hover{outline:0;border-color:#435aa0;background-color:#3865f9;background-image:-webkit-gradient(linear,left top,left bottom,from(#7691fb),color-stop(.5,#5075fb),color-stop(.5,#3865f9),to(#3665fa));background-image:-moz-linear-gradient(to bottom,#7691fb,#5075fb 50%,#3865f9 50%,#3665fa);background-image:-webkit-linear-gradient(to bottom,#7691fb,#5075fb 50%,#3865f9 50%,#3665fa);background-image:linear-gradient(to bottom,#7691fb,#5075fb 50%,#3865f9 50%,#3665fa);color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,.42)}.annotator-editor a:focus:after,.annotator-editor a:hover:after{margin-top:-8px;background-position:0 -105px}.annotator-editor a:active,.annotator-filter .annotator-filter-navigation button:active{border-color:#700c49;background-color:#d12e8e;background-image:-webkit-gradient(linear,left top,left bottom,from(#fc7cca),color-stop(.5,#e85db2),color-stop(.5,#d12e8e),to(#ff009c));background-image:-moz-linear-gradient(to bottom,#fc7cca,#e85db2 50%,#d12e8e 50%,#ff009c);background-image:-webkit-linear-gradient(to bottom,#fc7cca,#e85db2 50%,#d12e8e 50%,#ff009c);background-image:linear-gradient(to bottom,#fc7cca,#e85db2 50%,#d12e8e 50%,#ff009c)}.annotator-editor a.annotator-save:after{background-position:0 -120px}.annotator-editor a.annotator-save.annotator-focus:after,.annotator-editor a.annotator-save:focus:after,.annotator-editor a.annotator-save:hover:after{margin-top:-8px;background-position:0 -135px}.annotator-editor .annotator-widget:after{background-position:0 -30px}.annotator-editor.annotator-invert-y .annotator-widget .annotator-controls{background-color:#f2f2f2}.annotator-editor.annotator-invert-y .annotator-widget:after{background-position:0 -45px;height:11px}.annotator-resize{position:absolute;top:0;right:0;width:12px;height:12px;background-position:2px -150px}.annotator-invert-x .annotator-resize{right:auto;left:0;background-position:0 -195px}.annotator-invert-y .annotator-resize{top:auto;bottom:0;background-position:2px -165px}.annotator-invert-y.annotator-invert-x .annotator-resize{background-position:0 -180px}.annotator-notice{color:#fff;position:fixed;top:-54px;left:0;width:100%;font-size:14px;line-height:50px;text-align:center;background:#000;background:rgba(0,0,0,.9);border-bottom:4px solid #d4d4d4;-webkit-transition:top .4s ease-out;-moz-transition:top .4s ease-out;-o-transition:top .4s ease-out;transition:top .4s ease-out}.annotator-notice-success{border-color:#3665f9}.annotator-notice-error{border-color:#ff7e00}.annotator-notice p{margin:0}.annotator-notice a{color:#fff}.annotator-notice-show{top:0}.annotator-tags{margin-bottom:-2px}.annotator-tags .annotator-tag{display:inline-block;padding:0 8px;margin-bottom:2px;line-height:1.6;font-weight:700;background-color:#e6e6e6;-webkit-border-radius:8px;-moz-border-radius:8px;-o-border-radius:8px;border-radius:8px}.annotator-filter{z-index:1010;position:fixed;top:0;right:0;left:0;text-align:left;line-height:0;border:none;border-bottom:1px solid #878787;padding-left:10px;padding-right:10px;-webkit-border-radius:0;-moz-border-radius:0;-o-border-radius:0;border-radius:0;-webkit-box-shadow:inset 0 -1px 0 rgba(255,255,255,.3);-moz-box-shadow:inset 0 -1px 0 rgba(255,255,255,.3);-o-box-shadow:inset 0 -1px 0 rgba(255,255,255,.3);box-shadow:inset 0 -1px 0 rgba(255,255,255,.3)}.annotator-filter strong{font-size:12px;font-weight:700;color:#3c3c3c;text-shadow:0 1px 0 rgba(255,255,255,.7);position:relative;top:-9px}.annotator-filter .annotator-filter-navigation,.annotator-filter .annotator-filter-property{position:relative;display:inline-block;overflow:hidden;line-height:10px;padding:2px 0;margin-right:8px}.annotator-filter .annotator-filter-navigation button,.annotator-filter .annotator-filter-property label{text-align:left;display:block;float:left;line-height:20px;-webkit-border-radius:10px 0 0 10px;-moz-border-radius:10px 0 0 10px;-o-border-radius:10px 0 0 10px;border-radius:10px 0 0 10px}.annotator-filter .annotator-filter-property label{padding-left:8px}.annotator-filter .annotator-filter-property input{display:block;float:right;-webkit-appearance:none;border:1px solid #878787;border-left:none;padding:2px 4px;line-height:16px;min-height:16px;font-size:12px;width:150px;color:#333;background-color:#f8f8f8;-webkit-border-radius:0 10px 10px 0;-moz-border-radius:0 10px 10px 0;-o-border-radius:0 10px 10px 0;border-radius:0 10px 10px 0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.2);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,.2);-o-box-shadow:inset 0 1px 1px rgba(0,0,0,.2);box-shadow:inset 0 1px 1px rgba(0,0,0,.2)}.annotator-filter .annotator-filter-property input:focus{outline:0;background-color:#fff}.annotator-filter .annotator-filter-clear{position:absolute;right:3px;top:6px;border:none;text-indent:-900em;width:15px;height:15px;background-position:0 -90px;opacity:.4}.annotator-filter .annotator-filter-clear:focus,.annotator-filter .annotator-filter-clear:hover{opacity:.8}.annotator-filter .annotator-filter-clear:active{opacity:1}.annotator-filter .annotator-filter-navigation button{border:1px solid #a2a2a2;padding:0;text-indent:-900px;width:20px;min-height:22px;-webkit-box-shadow:inset 0 0 5px rgba(255,255,255,.2),inset 0 0 1px rgba(255,255,255,.8);-moz-box-shadow:inset 0 0 5px rgba(255,255,255,.2),inset 0 0 1px rgba(255,255,255,.8);-o-box-shadow:inset 0 0 5px rgba(255,255,255,.2),inset 0 0 1px rgba(255,255,255,.8);box-shadow:inset 0 0 5px rgba(255,255,255,.2),inset 0 0 1px rgba(255,255,255,.8)}.annotator-filter .annotator-filter-navigation button,.annotator-filter .annotator-filter-navigation button:focus,.annotator-filter .annotator-filter-navigation button:hover{color:transparent}.annotator-filter .annotator-filter-navigation button:after{position:absolute;top:8px;left:8px;content:"";display:block;width:9px;height:9px;background-position:0 -210px}.annotator-filter .annotator-filter-navigation button:hover:after{background-position:0 -225px}.annotator-filter .annotator-filter-navigation .annotator-filter-next{-webkit-border-radius:0 10px 10px 0;-moz-border-radius:0 10px 10px 0;-o-border-radius:0 10px 10px 0;border-radius:0 10px 10px 0;border-left:none}.annotator-filter .annotator-filter-navigation .annotator-filter-next:after{left:auto;right:7px;background-position:0 -240px}.annotator-filter .annotator-filter-navigation .annotator-filter-next:hover:after{background-position:0 -255px}.annotator-hl-active{background:#FFFF0A;background:rgba(255,255,10,.8);-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorstr=#CCFFFF0A, endColorstr=#CCFFFF0A)"}.annotator-hl-filtered{background-color:transparent}'; -},{}],6:[function(a,b,c){!function(a,c){"object"==typeof b&&"object"==typeof b.exports?b.exports=a.document?c(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return c(a)}:c(a)}("undefined"!=typeof window?window:this,function(a,b){function c(a){var b=!!a&&"length"in a&&a.length,c=na.type(a);return"function"===c||na.isWindow(a)?!1:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}function d(a,b,c){if(na.isFunction(b))return na.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return na.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(xa.test(b))return na.filter(b,a,c);b=na.filter(b,a)}return na.grep(a,function(a){return na.inArray(a,b)>-1!==c})}function e(a,b){do a=a[b];while(a&&1!==a.nodeType);return a}function f(a){var b={};return na.each(a.match(Da)||[],function(a,c){b[c]=!0}),b}function g(){da.addEventListener?(da.removeEventListener("DOMContentLoaded",h),a.removeEventListener("load",h)):(da.detachEvent("onreadystatechange",h),a.detachEvent("onload",h))}function h(){(da.addEventListener||"load"===a.event.type||"complete"===da.readyState)&&(g(),na.ready())}function i(a,b,c){if(void 0===c&&1===a.nodeType){var d="data-"+b.replace(Ia,"-$1").toLowerCase();if(c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:Ha.test(c)?na.parseJSON(c):c}catch(e){}na.data(a,b,c)}else c=void 0}return c}function j(a){var b;for(b in a)if(("data"!==b||!na.isEmptyObject(a[b]))&&"toJSON"!==b)return!1;return!0}function k(a,b,c,d){if(Ga(a)){var e,f,g=na.expando,h=a.nodeType,i=h?na.cache:a,j=h?a[g]:a[g]&&g;if(j&&i[j]&&(d||i[j].data)||void 0!==c||"string"!=typeof b)return j||(j=h?a[g]=ca.pop()||na.guid++:g),i[j]||(i[j]=h?{}:{toJSON:na.noop}),"object"!=typeof b&&"function"!=typeof b||(d?i[j]=na.extend(i[j],b):i[j].data=na.extend(i[j].data,b)),f=i[j],d||(f.data||(f.data={}),f=f.data),void 0!==c&&(f[na.camelCase(b)]=c),"string"==typeof b?(e=f[b],null==e&&(e=f[na.camelCase(b)])):e=f,e}}function l(a,b,c){if(Ga(a)){var d,e,f=a.nodeType,g=f?na.cache:a,h=f?a[na.expando]:na.expando;if(g[h]){if(b&&(d=c?g[h]:g[h].data)){na.isArray(b)?b=b.concat(na.map(b,na.camelCase)):b in d?b=[b]:(b=na.camelCase(b),b=b in d?[b]:b.split(" ")),e=b.length;for(;e--;)delete d[b[e]];if(c?!j(d):!na.isEmptyObject(d))return}(c||(delete g[h].data,j(g[h])))&&(f?na.cleanData([a],!0):la.deleteExpando||g!=g.window?delete g[h]:g[h]=void 0)}}}function m(a,b,c,d){var e,f=1,g=20,h=d?function(){return d.cur()}:function(){return na.css(a,b,"")},i=h(),j=c&&c[3]||(na.cssNumber[b]?"":"px"),k=(na.cssNumber[b]||"px"!==j&&+i)&&Ka.exec(na.css(a,b));if(k&&k[3]!==j){j=j||k[3],c=c||[],k=+i||1;do f=f||".5",k/=f,na.style(a,b,k+j);while(f!==(f=h()/i)&&1!==f&&--g)}return c&&(k=+k||+i||0,e=c[1]?k+(c[1]+1)*c[2]:+c[2],d&&(d.unit=j,d.start=k,d.end=e)),e}function n(a){var b=Sa.split("|"),c=a.createDocumentFragment();if(c.createElement)for(;b.length;)c.createElement(b.pop());return c}function o(a,b){var c,d,e=0,f="undefined"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||"*"):"undefined"!=typeof a.querySelectorAll?a.querySelectorAll(b||"*"):void 0;if(!f)for(f=[],c=a.childNodes||a;null!=(d=c[e]);e++)!b||na.nodeName(d,b)?f.push(d):na.merge(f,o(d,b));return void 0===b||b&&na.nodeName(a,b)?na.merge([a],f):f}function p(a,b){for(var c,d=0;null!=(c=a[d]);d++)na._data(c,"globalEval",!b||na._data(b[d],"globalEval"))}function q(a){Oa.test(a.type)&&(a.defaultChecked=a.checked)}function r(a,b,c,d,e){for(var f,g,h,i,j,k,l,m=a.length,r=n(b),s=[],t=0;m>t;t++)if(g=a[t],g||0===g)if("object"===na.type(g))na.merge(s,g.nodeType?[g]:g);else if(Ua.test(g)){for(i=i||r.appendChild(b.createElement("div")),j=(Pa.exec(g)||["",""])[1].toLowerCase(),l=Ta[j]||Ta._default,i.innerHTML=l[1]+na.htmlPrefilter(g)+l[2],f=l[0];f--;)i=i.lastChild;if(!la.leadingWhitespace&&Ra.test(g)&&s.push(b.createTextNode(Ra.exec(g)[0])),!la.tbody)for(g="table"!==j||Va.test(g)?""!==l[1]||Va.test(g)?0:i:i.firstChild,f=g&&g.childNodes.length;f--;)na.nodeName(k=g.childNodes[f],"tbody")&&!k.childNodes.length&&g.removeChild(k);for(na.merge(s,i.childNodes),i.textContent="";i.firstChild;)i.removeChild(i.firstChild);i=r.lastChild}else s.push(b.createTextNode(g));for(i&&r.removeChild(i),la.appendChecked||na.grep(o(s,"input"),q),t=0;g=s[t++];)if(d&&na.inArray(g,d)>-1)e&&e.push(g);else if(h=na.contains(g.ownerDocument,g),i=o(r.appendChild(g),"script"),h&&p(i),c)for(f=0;g=i[f++];)Qa.test(g.type||"")&&c.push(g);return i=null,r}function s(){return!0}function t(){return!1}function u(){try{return da.activeElement}catch(a){}}function v(a,b,c,d,e,f){var g,h;if("object"==typeof b){"string"!=typeof c&&(d=d||c,c=void 0);for(h in b)v(a,h,c,d,b[h],f);return a}if(null==d&&null==e?(e=c,d=c=void 0):null==e&&("string"==typeof c?(e=d,d=void 0):(e=d,d=c,c=void 0)),e===!1)e=t;else if(!e)return a;return 1===f&&(g=e,e=function(a){return na().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=na.guid++)),a.each(function(){na.event.add(this,b,e,d,c)})}function w(a,b){return na.nodeName(a,"table")&&na.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function x(a){return a.type=(null!==na.find.attr(a,"type"))+"/"+a.type,a}function y(a){var b=eb.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function z(a,b){if(1===b.nodeType&&na.hasData(a)){var c,d,e,f=na._data(a),g=na._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;e>d;d++)na.event.add(b,c,h[c][d])}g.data&&(g.data=na.extend({},g.data))}}function A(a,b){var c,d,e;if(1===b.nodeType){if(c=b.nodeName.toLowerCase(),!la.noCloneEvent&&b[na.expando]){e=na._data(b);for(d in e.events)na.removeEvent(b,d,e.handle);b.removeAttribute(na.expando)}"script"===c&&b.text!==a.text?(x(b).text=a.text,y(b)):"object"===c?(b.parentNode&&(b.outerHTML=a.outerHTML),la.html5Clone&&a.innerHTML&&!na.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):"input"===c&&Oa.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):"option"===c?b.defaultSelected=b.selected=a.defaultSelected:"input"!==c&&"textarea"!==c||(b.defaultValue=a.defaultValue)}}function B(a,b,c,d){b=fa.apply([],b);var e,f,g,h,i,j,k=0,l=a.length,m=l-1,n=b[0],p=na.isFunction(n);if(p||l>1&&"string"==typeof n&&!la.checkClone&&db.test(n))return a.each(function(e){var f=a.eq(e);p&&(b[0]=n.call(this,e,f.html())),B(f,b,c,d)});if(l&&(j=r(b,a[0].ownerDocument,!1,a,d),e=j.firstChild,1===j.childNodes.length&&(j=e),e||d)){for(h=na.map(o(j,"script"),x),g=h.length;l>k;k++)f=j,k!==m&&(f=na.clone(f,!0,!0),g&&na.merge(h,o(f,"script"))),c.call(a[k],f,k);if(g)for(i=h[h.length-1].ownerDocument,na.map(h,y),k=0;g>k;k++)f=h[k],Qa.test(f.type||"")&&!na._data(f,"globalEval")&&na.contains(i,f)&&(f.src?na._evalUrl&&na._evalUrl(f.src):na.globalEval((f.text||f.textContent||f.innerHTML||"").replace(fb,"")));j=e=null}return a}function C(a,b,c){for(var d,e=b?na.filter(b,a):a,f=0;null!=(d=e[f]);f++)c||1!==d.nodeType||na.cleanData(o(d)),d.parentNode&&(c&&na.contains(d.ownerDocument,d)&&p(o(d,"script")),d.parentNode.removeChild(d));return a}function D(a,b){var c=na(b.createElement(a)).appendTo(b.body),d=na.css(c[0],"display");return c.detach(),d}function E(a){var b=da,c=jb[a];return c||(c=D(a,b),"none"!==c&&c||(ib=(ib||na("