]> git.immae.eu Git - github/wallabag/wallabag.git/commitdiff
Merge pull request #3022 from wallabag/webpack
authorThomas Citharel <tcit@tcit.fr>
Tue, 9 May 2017 09:43:48 +0000 (11:43 +0200)
committerGitHub <noreply@github.com>
Tue, 9 May 2017 09:43:48 +0000 (11:43 +0200)
Adds Webpack support and remove Grunt

34 files changed:
app/config/config.yml
docs/en/developer/console_commands.rst [new file with mode: 0644]
docs/en/user/import.rst
docs/fr/developer/console_commands.rst [new file with mode: 0644]
src/Wallabag/ApiBundle/Controller/EntryRestController.php
src/Wallabag/CoreBundle/Command/CleanDuplicatesCommand.php [new file with mode: 0644]
src/Wallabag/CoreBundle/Command/InstallCommand.php
src/Wallabag/CoreBundle/DependencyInjection/Configuration.php
src/Wallabag/CoreBundle/DependencyInjection/WallabagCoreExtension.php
src/Wallabag/CoreBundle/Repository/EntryRepository.php
src/Wallabag/CoreBundle/Resources/translations/messages.da.yml
src/Wallabag/CoreBundle/Resources/translations/messages.de.yml
src/Wallabag/CoreBundle/Resources/translations/messages.en.yml
src/Wallabag/CoreBundle/Resources/translations/messages.es.yml
src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml
src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml
src/Wallabag/CoreBundle/Resources/translations/messages.it.yml
src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml
src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml
src/Wallabag/CoreBundle/Resources/translations/messages.pt.yml
src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml
src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml
src/Wallabag/ImportBundle/Command/ImportCommand.php
src/Wallabag/UserBundle/Controller/ManageController.php
src/Wallabag/UserBundle/Form/SearchUserType.php [new file with mode: 0644]
src/Wallabag/UserBundle/Repository/UserRepository.php
src/Wallabag/UserBundle/Resources/views/Manage/index.html.twig
tests/Wallabag/ApiBundle/Controller/EntryRestControllerTest.php
tests/Wallabag/CoreBundle/Command/CleanDuplicatesCommandTest.php [new file with mode: 0644]
tests/Wallabag/CoreBundle/Command/ExportCommandTest.php
tests/Wallabag/CoreBundle/Command/InstallCommandTest.php
tests/Wallabag/CoreBundle/Helper/ContentProxyTest.php
tests/Wallabag/ImportBundle/Command/ImportCommandTest.php
tests/Wallabag/UserBundle/Controller/ManageControllerTest.php

index c076aea968f7095c42ccfa5272bddb9e19dde946..116bb04c15c37f6f0f49c10de7044d1d8de7208f 100644 (file)
@@ -59,6 +59,7 @@ wallabag_core:
     list_mode: 0
     fetching_error_message: |
         wallabag can't retrieve contents for this article. Please <a href="http://doc.wallabag.org/en/master/user/errors_during_fetching.html#how-can-i-help-to-fix-that">troubleshoot this issue</a>.
+    api_limit_mass_actions: 10
 
 wallabag_user:
     registration_enabled: "%fosuser_registration%"
diff --git a/docs/en/developer/console_commands.rst b/docs/en/developer/console_commands.rst
new file mode 100644 (file)
index 0000000..85a8a09
--- /dev/null
@@ -0,0 +1,30 @@
+Console Commands
+================
+
+wallabag has a number of CLI commands to manage a number of tasks. You can list all the commands by executing `bin/console` in the wallabag folder.
+
+Each command has a help accessible through `bin/console help %command%`.
+
+.. note::
+
+    If you're in a production environment, remember to add `-e prod` to each command.
+
+Notable commands
+----------------
+
+* `assets:install`: May be helpful if assets are missing.
+* `cache:clear`: should be run after each update (included in `make update`).
+* `doctrine:migrations:status`: Output the status of your database migrations.
+* `fos:user:activate`: Manually activate an user.
+* `fos:user:change-password`: Change a password for an user.
+* `fos:user:create`: Create an user.
+* `fos:user:deactivate`: Deactivate an user (not deleted).
+* `fos:user:demote`: Removes a role from an user, typically admin rights.
+* `fos:user:promote`: Adds a role to an user, typically admin rights.
+* `rabbitmq:*`: May be useful if you're using RabbitMQ.
+* `wallabag:clean-duplicates`: Removes all entry duplicates for one user or all users
+* `wallabag:export`: Exports all entries for an user. You can choose the output path of the file.
+* `wallabag:import`: Import entries to different formats to an user account.
+* `wallabag:import:redis-worker`: Useful if you use Redis.
+* `wallabag:install`: (re)Install wallabag
+* `wallabag:tag:all`: Tag all entries for an user using his/her tagging rules.
index 50bb1de367f59c0d3648adc2ba4d6909bd5db0b6..f6aaa373d2c768c16dbdb196b44cd93bf721616d 100644 (file)
@@ -77,7 +77,7 @@ From Instapaper
 ---------------
 
 Export your Instapaper data
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 On the settings (`https://www.instapaper.com/user <https://www.instapaper.com/user>`_) page, click on "Download .CSV file" in the "Export" section. A CSV file will be downloaded (like ``instapaper-export.csv``).
 
@@ -133,16 +133,21 @@ If you have a CLI access on your web server, you can execute this command to imp
 
 ::
 
-    bin/console wallabag:import 1 ~/Downloads/wallabag-export-1-2016-04-05.json --env=prod
+    bin/console wallabag:import username ~/Downloads/wallabag-export-1-2016-04-05.json --env=prod
 
 Please replace values:
 
-* ``1`` is the user identifier in database (The ID of the first user created on wallabag is 1)
+* ``username`` is the user's username
 * ``~/Downloads/wallabag-export-1-2016-04-05.json`` is the path of your wallabag v1 export
 
-If you want to mark all these entries as read, you can add the ``--markAsRead`` option.
+.. note::
+    If you want to mark all these entries as read, you can add the ``--markAsRead`` option.
 
-To import a wallabag v2 file, you need to add the option ``--importer=v2``.
+.. note::
+    To import a wallabag v2 file, you need to add the option ``--importer=v2``.
+
+.. note::
+    If you want to pass the user id of the user instead of it's username, add the option ``--useUserId=true``.
 
 You'll have this in return:
 
diff --git a/docs/fr/developer/console_commands.rst b/docs/fr/developer/console_commands.rst
new file mode 100644 (file)
index 0000000..1b222b3
--- /dev/null
@@ -0,0 +1,30 @@
+Actions en ligne de commande
+============================
+
+wallabag a un certain nombre de commandes CLI pour effectuer des tâches. Vous pouvez lister toutes les commandes en exécutant `bin/console` dans le dossier d'installation de wallabag.
+
+Chaque commande a une aide correspondante accessible via `bin/console help %command%`.
+
+.. note::
+
+    Si vous êtes dans un environnement de production, souvenez-vous d'ajouter `-e prod` à chaque commande.
+
+Commandes notables
+------------------
+
+* `assets:install`: Peut-être utile si les *assets* sont manquants.
+* `cache:clear`: doit être exécuté après chaque mise à jour (appelé dans `make update`).
+* `doctrine:migrations:status`: Montre le statut de vos migrations de vos bases de données.
+* `fos:user:activate`: Activer manuellement un utilisateur.
+* `fos:user:change-password`: Changer le mot de passe pour un utilisateur.
+* `fos:user:create`: Créer un utilisateur.
+* `fos:user:deactivate`: Désactiver un utilisateur (non supprimé).
+* `fos:user:demote`: Supprimer un rôle d'un utilisateur, typiquement les droits d'administration.
+* `fos:user:promote`: Ajoute un rôle à un utilisateur, typiquement les droits d'administration.
+* `rabbitmq:*`: Peut-être utile si vous utilisez RabbitMQ.
+* `wallabag:clean-duplicates`: Supprime tous les articles dupliqués pour un utilisateur ou bien tous.
+* `wallabag:export`: Exporte tous les articles pour un utilisateur. Vous pouvez choisir le chemin du fichier exporté.
+* `wallabag:import`: Importe les articles en différents formats dans un compte utilisateur.
+* `wallabag:import:redis-worker`: Utile si vous utilisez Redis.
+* `wallabag:install`: (ré)Installer wallabag
+* `wallabag:tag:all`: Tagger tous les articles pour un utilisateur ou une utilisatrice en utilisant ses règles de tags automatiques.
index 7590efbb16b5325102ccea35afdae450bd7581b6..dbff606547c1e59ee96447046bee322d44c7bf35 100644 (file)
@@ -5,6 +5,7 @@ namespace Wallabag\ApiBundle\Controller;
 use Hateoas\Configuration\Route;
 use Hateoas\Representation\Factory\PagerfantaFactory;
 use Nelmio\ApiDocBundle\Annotation\ApiDoc;
+use Symfony\Component\HttpKernel\Exception\HttpException;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\JsonResponse;
 use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
@@ -44,9 +45,7 @@ class EntryRestController extends WallabagRestController
                 $results[$url] = $res instanceof Entry ? $res->getId() : false;
             }
 
-            $json = $this->get('serializer')->serialize($results, 'json');
-
-            return (new JsonResponse())->setJson($json);
+            return $this->sendResponse($results);
         }
 
         // let's see if it is a simple url?
@@ -62,9 +61,7 @@ class EntryRestController extends WallabagRestController
 
         $exists = $res instanceof Entry ? $res->getId() : false;
 
-        $json = $this->get('serializer')->serialize(['exists' => $exists], 'json');
-
-        return (new JsonResponse())->setJson($json);
+        return $this->sendResponse(['exists' => $exists]);
     }
 
     /**
@@ -124,9 +121,7 @@ class EntryRestController extends WallabagRestController
             )
         );
 
-        $json = $this->get('serializer')->serialize($paginatedCollection, 'json');
-
-        return (new JsonResponse())->setJson($json);
+        return $this->sendResponse($paginatedCollection);
     }
 
     /**
@@ -145,9 +140,7 @@ class EntryRestController extends WallabagRestController
         $this->validateAuthentication();
         $this->validateUserAccess($entry->getUser()->getId());
 
-        $json = $this->get('serializer')->serialize($entry, 'json');
-
-        return (new JsonResponse())->setJson($json);
+        return $this->sendResponse($entry);
     }
 
     /**
@@ -172,6 +165,110 @@ class EntryRestController extends WallabagRestController
             ->exportAs($request->attributes->get('_format'));
     }
 
+    /**
+     * Handles an entries list and delete URL.
+     *
+     * @ApiDoc(
+     *       parameters={
+     *          {"name"="urls", "dataType"="string", "required"=true, "format"="A JSON array of urls [{'url': 'http://...'}, {'url': 'http://...'}]", "description"="Urls (as an array) to delete."}
+     *       }
+     * )
+     *
+     * @return JsonResponse
+     */
+    public function deleteEntriesListAction(Request $request)
+    {
+        $this->validateAuthentication();
+
+        $urls = json_decode($request->query->get('urls', []));
+
+        if (empty($urls)) {
+            return $this->sendResponse([]);
+        }
+
+        $results = [];
+
+        // handle multiple urls
+        foreach ($urls as $key => $url) {
+            $entry = $this->get('wallabag_core.entry_repository')->findByUrlAndUserId(
+                $url,
+                $this->getUser()->getId()
+            );
+
+            $results[$key]['url'] = $url;
+
+            if (false !== $entry) {
+                $em = $this->getDoctrine()->getManager();
+                $em->remove($entry);
+                $em->flush();
+
+                // entry deleted, dispatch event about it!
+                $this->get('event_dispatcher')->dispatch(EntryDeletedEvent::NAME, new EntryDeletedEvent($entry));
+            }
+
+            $results[$key]['entry'] = $entry instanceof Entry ? true : false;
+        }
+
+        return $this->sendResponse($results);
+    }
+
+    /**
+     * Handles an entries list and create URL.
+     *
+     * @ApiDoc(
+     *       parameters={
+     *          {"name"="urls", "dataType"="string", "required"=true, "format"="A JSON array of urls [{'url': 'http://...'}, {'url': 'http://...'}]", "description"="Urls (as an array) to create."}
+     *       }
+     * )
+     *
+     * @return JsonResponse
+     *
+     * @throws HttpException When limit is reached
+     */
+    public function postEntriesListAction(Request $request)
+    {
+        $this->validateAuthentication();
+
+        $urls = json_decode($request->query->get('urls', []));
+        $results = [];
+
+        $limit = $this->container->getParameter('wallabag_core.api_limit_mass_actions');
+
+        if (count($urls) > $limit) {
+            throw new HttpException(400, 'API limit reached');
+        }
+
+        // handle multiple urls
+        if (!empty($urls)) {
+            foreach ($urls as $key => $url) {
+                $entry = $this->get('wallabag_core.entry_repository')->findByUrlAndUserId(
+                    $url,
+                    $this->getUser()->getId()
+                );
+
+                $results[$key]['url'] = $url;
+
+                if (false === $entry) {
+                    $entry = $this->get('wallabag_core.content_proxy')->updateEntry(
+                        new Entry($this->getUser()),
+                        $url
+                    );
+                }
+
+                $em = $this->getDoctrine()->getManager();
+                $em->persist($entry);
+                $em->flush();
+
+                $results[$key]['entry'] = $entry instanceof Entry ? $entry->getId() : false;
+
+                // entry saved, dispatch event about it!
+                $this->get('event_dispatcher')->dispatch(EntrySavedEvent::NAME, new EntrySavedEvent($entry));
+            }
+        }
+
+        return $this->sendResponse($results);
+    }
+
     /**
      * Create an entry.
      *
@@ -229,9 +326,7 @@ class EntryRestController extends WallabagRestController
         // entry saved, dispatch event about it!
         $this->get('event_dispatcher')->dispatch(EntrySavedEvent::NAME, new EntrySavedEvent($entry));
 
-        $json = $this->get('serializer')->serialize($entry, 'json');
-
-        return (new JsonResponse())->setJson($json);
+        return $this->sendResponse($entry);
     }
 
     /**
@@ -280,9 +375,7 @@ class EntryRestController extends WallabagRestController
         $em = $this->getDoctrine()->getManager();
         $em->flush();
 
-        $json = $this->get('serializer')->serialize($entry, 'json');
-
-        return (new JsonResponse())->setJson($json);
+        return $this->sendResponse($entry);
     }
 
     /**
@@ -325,9 +418,7 @@ class EntryRestController extends WallabagRestController
         // entry saved, dispatch event about it!
         $this->get('event_dispatcher')->dispatch(EntrySavedEvent::NAME, new EntrySavedEvent($entry));
 
-        $json = $this->get('serializer')->serialize($entry, 'json');
-
-        return (new JsonResponse())->setJson($json);
+        return $this->sendResponse($entry);
     }
 
     /**
@@ -353,9 +444,7 @@ class EntryRestController extends WallabagRestController
         // entry deleted, dispatch event about it!
         $this->get('event_dispatcher')->dispatch(EntryDeletedEvent::NAME, new EntryDeletedEvent($entry));
 
-        $json = $this->get('serializer')->serialize($entry, 'json');
-
-        return (new JsonResponse())->setJson($json);
+        return $this->sendResponse($entry);
     }
 
     /**
@@ -374,9 +463,7 @@ class EntryRestController extends WallabagRestController
         $this->validateAuthentication();
         $this->validateUserAccess($entry->getUser()->getId());
 
-        $json = $this->get('serializer')->serialize($entry->getTags(), 'json');
-
-        return (new JsonResponse())->setJson($json);
+        return $this->sendResponse($entry->getTags());
     }
 
     /**
@@ -407,9 +494,7 @@ class EntryRestController extends WallabagRestController
         $em->persist($entry);
         $em->flush();
 
-        $json = $this->get('serializer')->serialize($entry, 'json');
-
-        return (new JsonResponse())->setJson($json);
+        return $this->sendResponse($entry);
     }
 
     /**
@@ -434,9 +519,7 @@ class EntryRestController extends WallabagRestController
         $em->persist($entry);
         $em->flush();
 
-        $json = $this->get('serializer')->serialize($entry, 'json');
-
-        return (new JsonResponse())->setJson($json);
+        return $this->sendResponse($entry);
     }
 
     /**
@@ -455,45 +538,46 @@ class EntryRestController extends WallabagRestController
         $this->validateAuthentication();
 
         $list = json_decode($request->query->get('list', []));
-        $results = [];
+
+        if (empty($list)) {
+            return $this->sendResponse([]);
+        }
 
         // handle multiple urls
-        if (!empty($list)) {
-            foreach ($list as $key => $element) {
-                $entry = $this->get('wallabag_core.entry_repository')->findByUrlAndUserId(
-                    $element->url,
-                    $this->getUser()->getId()
-                );
+        $results = [];
 
-                $results[$key]['url'] = $element->url;
-                $results[$key]['entry'] = $entry instanceof Entry ? $entry->getId() : false;
+        foreach ($list as $key => $element) {
+            $entry = $this->get('wallabag_core.entry_repository')->findByUrlAndUserId(
+                $element->url,
+                $this->getUser()->getId()
+            );
 
-                $tags = $element->tags;
+            $results[$key]['url'] = $element->url;
+            $results[$key]['entry'] = $entry instanceof Entry ? $entry->getId() : false;
 
-                if (false !== $entry && !(empty($tags))) {
-                    $tags = explode(',', $tags);
-                    foreach ($tags as $label) {
-                        $label = trim($label);
+            $tags = $element->tags;
 
-                        $tag = $this->getDoctrine()
-                            ->getRepository('WallabagCoreBundle:Tag')
-                            ->findOneByLabel($label);
+            if (false !== $entry && !(empty($tags))) {
+                $tags = explode(',', $tags);
+                foreach ($tags as $label) {
+                    $label = trim($label);
 
-                        if (false !== $tag) {
-                            $entry->removeTag($tag);
-                        }
-                    }
+                    $tag = $this->getDoctrine()
+                        ->getRepository('WallabagCoreBundle:Tag')
+                        ->findOneByLabel($label);
 
-                    $em = $this->getDoctrine()->getManager();
-                    $em->persist($entry);
-                    $em->flush();
+                    if (false !== $tag) {
+                        $entry->removeTag($tag);
+                    }
                 }
+
+                $em = $this->getDoctrine()->getManager();
+                $em->persist($entry);
+                $em->flush();
             }
         }
 
-        $json = $this->get('serializer')->serialize($results, 'json');
-
-        return (new JsonResponse())->setJson($json);
+        return $this->sendResponse($results);
     }
 
     /**
@@ -512,32 +596,47 @@ class EntryRestController extends WallabagRestController
         $this->validateAuthentication();
 
         $list = json_decode($request->query->get('list', []));
+
+        if (empty($list)) {
+            return $this->sendResponse([]);
+        }
+
         $results = [];
 
         // handle multiple urls
-        if (!empty($list)) {
-            foreach ($list as $key => $element) {
-                $entry = $this->get('wallabag_core.entry_repository')->findByUrlAndUserId(
-                    $element->url,
-                    $this->getUser()->getId()
-                );
+        foreach ($list as $key => $element) {
+            $entry = $this->get('wallabag_core.entry_repository')->findByUrlAndUserId(
+                $element->url,
+                $this->getUser()->getId()
+            );
 
-                $results[$key]['url'] = $element->url;
-                $results[$key]['entry'] = $entry instanceof Entry ? $entry->getId() : false;
+            $results[$key]['url'] = $element->url;
+            $results[$key]['entry'] = $entry instanceof Entry ? $entry->getId() : false;
 
-                $tags = $element->tags;
+            $tags = $element->tags;
 
-                if (false !== $entry && !(empty($tags))) {
-                    $this->get('wallabag_core.content_proxy')->assignTagsToEntry($entry, $tags);
+            if (false !== $entry && !(empty($tags))) {
+                $this->get('wallabag_core.content_proxy')->assignTagsToEntry($entry, $tags);
 
-                    $em = $this->getDoctrine()->getManager();
-                    $em->persist($entry);
-                    $em->flush();
-                }
+                $em = $this->getDoctrine()->getManager();
+                $em->persist($entry);
+                $em->flush();
             }
         }
 
-        $json = $this->get('serializer')->serialize($results, 'json');
+        return $this->sendResponse($results);
+    }
+
+    /**
+     * Shortcut to send data serialized in json.
+     *
+     * @param mixed $data
+     *
+     * @return JsonResponse
+     */
+    private function sendResponse($data)
+    {
+        $json = $this->get('serializer')->serialize($data, 'json');
 
         return (new JsonResponse())->setJson($json);
     }
diff --git a/src/Wallabag/CoreBundle/Command/CleanDuplicatesCommand.php b/src/Wallabag/CoreBundle/Command/CleanDuplicatesCommand.php
new file mode 100644 (file)
index 0000000..65f35d8
--- /dev/null
@@ -0,0 +1,119 @@
+<?php
+
+namespace Wallabag\CoreBundle\Command;
+
+use Doctrine\ORM\NoResultException;
+use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Wallabag\CoreBundle\Entity\Entry;
+use Wallabag\UserBundle\Entity\User;
+
+class CleanDuplicatesCommand extends ContainerAwareCommand
+{
+    /** @var OutputInterface */
+    protected $output;
+
+    protected $duplicates = 0;
+
+    protected function configure()
+    {
+        $this
+            ->setName('wallabag:clean-duplicates')
+            ->setDescription('Cleans the database for duplicates')
+            ->setHelp('This command helps you to clean your articles list in case of duplicates')
+            ->addArgument(
+                'username',
+                InputArgument::OPTIONAL,
+                'User to clean'
+            );
+    }
+
+    protected function execute(InputInterface $input, OutputInterface $output)
+    {
+        $this->output = $output;
+
+        $username = $input->getArgument('username');
+
+        if ($username) {
+            try {
+                $user = $this->getUser($username);
+                $this->cleanDuplicates($user);
+            } catch (NoResultException $e) {
+                $output->writeln(sprintf('<error>User "%s" not found.</error>', $username));
+
+                return 1;
+            }
+        } else {
+            $users = $this->getDoctrine()->getRepository('WallabagUserBundle:User')->findAll();
+
+            $output->writeln(sprintf('Cleaning through %d user accounts', count($users)));
+
+            foreach ($users as $user) {
+                $output->writeln(sprintf('Processing user %s', $user->getUsername()));
+                $this->cleanDuplicates($user);
+            }
+            $output->writeln(sprintf('Finished cleaning. %d duplicates found in total', $this->duplicates));
+        }
+
+        return 0;
+    }
+
+    /**
+     * @param User $user
+     */
+    private function cleanDuplicates(User $user)
+    {
+        $em = $this->getContainer()->get('doctrine.orm.entity_manager');
+        $repo = $this->getDoctrine()->getRepository('WallabagCoreBundle:Entry');
+
+        $entries = $repo->getAllEntriesIdAndUrl($user->getId());
+
+        $duplicatesCount = 0;
+        $urls = [];
+        foreach ($entries as $entry) {
+            $url = $this->similarUrl($entry['url']);
+
+            /* @var $entry Entry */
+            if (in_array($url, $urls)) {
+                ++$duplicatesCount;
+
+                $em->remove($repo->find($entry['id']));
+                $em->flush(); // Flushing at the end of the loop would require the instance not being online
+            } else {
+                $urls[] = $entry['url'];
+            }
+        }
+
+        $this->duplicates += $duplicatesCount;
+
+        $this->output->writeln(sprintf('Cleaned %d duplicates for user %s', $duplicatesCount, $user->getUserName()));
+    }
+
+    private function similarUrl($url)
+    {
+        if (in_array(substr($url, -1), ['/', '#'])) { // get rid of "/" and "#" and the end of urls
+            return substr($url, 0, strlen($url));
+        }
+
+        return $url;
+    }
+
+    /**
+     * Fetches a user from its username.
+     *
+     * @param string $username
+     *
+     * @return \Wallabag\UserBundle\Entity\User
+     */
+    private function getUser($username)
+    {
+        return $this->getDoctrine()->getRepository('WallabagUserBundle:User')->findOneByUserName($username);
+    }
+
+    private function getDoctrine()
+    {
+        return $this->getContainer()->get('doctrine');
+    }
+}
index 3c4d3f2597b243e53b083f451be4945369279b4b..0d9364f6aa4fb30a2c629f033126e254dbb5d722 100644 (file)
@@ -63,6 +63,7 @@ class InstallCommand extends ContainerAwareCommand
             ->setupDatabase()
             ->setupAdmin()
             ->setupConfig()
+            ->runMigrations()
         ;
 
         $output->writeln('<info>wallabag has been successfully installed.</info>');
@@ -71,7 +72,7 @@ class InstallCommand extends ContainerAwareCommand
 
     protected function checkRequirements()
     {
-        $this->defaultOutput->writeln('<info><comment>Step 1 of 4.</comment> Checking system requirements.</info>');
+        $this->defaultOutput->writeln('<info><comment>Step 1 of 5.</comment> Checking system requirements.</info>');
         $doctrineManager = $this->getContainer()->get('doctrine')->getManager();
 
         $rows = [];
@@ -175,11 +176,11 @@ class InstallCommand extends ContainerAwareCommand
 
     protected function setupDatabase()
     {
-        $this->defaultOutput->writeln('<info><comment>Step 2 of 4.</comment> Setting up database.</info>');
+        $this->defaultOutput->writeln('<info><comment>Step 2 of 5.</comment> Setting up database.</info>');
 
         // user want to reset everything? Don't care about what is already here
         if (true === $this->defaultInput->getOption('reset')) {
-            $this->defaultOutput->writeln('Droping database, creating database and schema, clearing the cache');
+            $this->defaultOutput->writeln('Dropping database, creating database and schema, clearing the cache');
 
             $this
                 ->runCommand('doctrine:database:drop', ['--force' => true])
@@ -211,7 +212,7 @@ class InstallCommand extends ContainerAwareCommand
         $question = new ConfirmationQuestion('It appears that your database already exists. Would you like to reset it? (y/N)', false);
 
         if ($questionHelper->ask($this->defaultInput, $this->defaultOutput, $question)) {
-            $this->defaultOutput->writeln('Droping database, creating database and schema');
+            $this->defaultOutput->writeln('Dropping database, creating database and schema');
 
             $this
                 ->runCommand('doctrine:database:drop', ['--force' => true])
@@ -221,7 +222,7 @@ class InstallCommand extends ContainerAwareCommand
         } elseif ($this->isSchemaPresent()) {
             $question = new ConfirmationQuestion('Seems like your database contains schema. Do you want to reset it? (y/N)', false);
             if ($questionHelper->ask($this->defaultInput, $this->defaultOutput, $question)) {
-                $this->defaultOutput->writeln('Droping schema and creating schema');
+                $this->defaultOutput->writeln('Dropping schema and creating schema');
 
                 $this
                     ->runCommand('doctrine:schema:drop', ['--force' => true])
@@ -246,7 +247,7 @@ class InstallCommand extends ContainerAwareCommand
 
     protected function setupAdmin()
     {
-        $this->defaultOutput->writeln('<info><comment>Step 3 of 4.</comment> Administration setup.</info>');
+        $this->defaultOutput->writeln('<info><comment>Step 3 of 5.</comment> Administration setup.</info>');
 
         $questionHelper = $this->getHelperSet()->get('question');
         $question = new ConfirmationQuestion('Would you like to create a new admin user (recommended) ? (Y/n)', true);
@@ -285,7 +286,7 @@ class InstallCommand extends ContainerAwareCommand
 
     protected function setupConfig()
     {
-        $this->defaultOutput->writeln('<info><comment>Step 4 of 4.</comment> Config setup.</info>');
+        $this->defaultOutput->writeln('<info><comment>Step 4 of 5.</comment> Config setup.</info>');
         $em = $this->getContainer()->get('doctrine.orm.entity_manager');
 
         // cleanup before insert new stuff
@@ -464,6 +465,14 @@ class InstallCommand extends ContainerAwareCommand
         return $this;
     }
 
+    protected function runMigrations()
+    {
+        $this->defaultOutput->writeln('<info><comment>Step 5 of 5.</comment> Run migrations.</info>');
+
+        $this
+            ->runCommand('doctrine:migrations:migrate', ['--no-interaction' => true]);
+    }
+
     /**
      * Run a command.
      *
index 006a18c397a6ceea925ef29dcd5ea254c528eb52..75b37729f38eb601c093bd9b786602c63b4c5291 100644 (file)
@@ -47,6 +47,9 @@ class Configuration implements ConfigurationInterface
                 ->scalarNode('list_mode')
                     ->defaultValue(1)
                 ->end()
+                ->scalarNode('api_limit_mass_actions')
+                    ->defaultValue(10)
+                ->end()
             ->end()
         ;
 
index aa9ee339adebd24a1e7d9282eb6b70714396e740..c075c19fd993c48d6c77ee88bf34f545d8d3e336 100644 (file)
@@ -26,6 +26,7 @@ class WallabagCoreExtension extends Extension
         $container->setParameter('wallabag_core.action_mark_as_read', $config['action_mark_as_read']);
         $container->setParameter('wallabag_core.list_mode', $config['list_mode']);
         $container->setParameter('wallabag_core.fetching_error_message', $config['fetching_error_message']);
+        $container->setParameter('wallabag_core.api_limit_mass_actions', $config['api_limit_mass_actions']);
 
         $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
         $loader->load('services.yml');
index 1f22e901ba4d537a2e757f9901a03987bc0b5bcf..6972e974eeeb1853d28472f5be3ebf1b881ea9fa 100644 (file)
@@ -379,4 +379,34 @@ class EntryRepository extends EntityRepository
             ->setParameter('userId', $userId)
             ->execute();
     }
+
+    /**
+     * Get id and url from all entries
+     * Used for the clean-duplicates command.
+     */
+    public function getAllEntriesIdAndUrl($userId)
+    {
+        $qb = $this->createQueryBuilder('e')
+            ->select('e.id, e.url')
+            ->where('e.user = :userid')->setParameter(':userid', $userId);
+
+        return $qb->getQuery()->getArrayResult();
+    }
+
+    /**
+     * Find all entries by url and owner.
+     *
+     * @param $url
+     * @param $userId
+     *
+     * @return array
+     */
+    public function findAllByUrlAndUserId($url, $userId)
+    {
+        return $this->createQueryBuilder('e')
+            ->where('e.url = :url')->setParameter('url', urldecode($url))
+            ->andWhere('e.user = :user_id')->setParameter('user_id', $userId)
+            ->getQuery()
+            ->getResult();
+    }
 }
index e5211b5730c69c6bbe84a6b617b25a7482013e40..57319af73d4ecc5e1f1d89097e63b6717c674fcd 100644 (file)
@@ -512,6 +512,8 @@ user:
         # delete: Delete
         # delete_confirm: Are you sure?
         # back_to_list: Back to list
+    search:
+        # placeholder: Filter by username or email
 
 error:
     # page_title: An error occurred
index 893a45648bfbd515d159c32534e452a4ad6fbe6b..a7bcecc6f5d57539bad8b655d6eb73cf1bfd9fbb 100644 (file)
@@ -513,6 +513,8 @@ user:
         delete: Löschen
         delete_confirm: Bist du sicher?
         back_to_list: Zurück zur Liste
+    search:
+        # placeholder: Filter by username or email
 
 error:
     page_title: Ein Fehler ist aufgetreten
index 4b74568304aa8a8078b407475a8b7dd354417879..1ef2874d26d2750713200168192efde7da84eee9 100644 (file)
@@ -513,6 +513,8 @@ user:
         delete: Delete
         delete_confirm: Are you sure?
         back_to_list: Back to list
+    search:
+        placeholder: Filter by username or email
 
 error:
     page_title: An error occurred
index 99d2585924902f3f9012bf9fb6869ab04cf802cf..6cd079b077352ffe8284a990a3900f17825785d5 100644 (file)
@@ -513,6 +513,8 @@ user:
         delete: Eliminar
         delete_confirm: ¿Estás seguro?
         back_to_list: Volver a la lista
+    search:
+        # placeholder: Filter by username or email
 
 error:
     page_title: Ha ocurrido un error
index ccd9d5557c715bd97664d5932cc11918a71beb7b..fb6e315ec6dc16a89d30743783526228c15249af 100644 (file)
@@ -513,6 +513,8 @@ user:
         # delete: Delete
         # delete_confirm: Are you sure?
         # back_to_list: Back to list
+    search:
+        # placeholder: Filter by username or email
 
 error:
     # page_title: An error occurred
index a0f100f7a138f9ddd9b259b01b45c54c64058841..ad886363550c7650670c5bc0b5fe1389b186d323 100644 (file)
@@ -46,7 +46,7 @@ footer:
         social: "Social"
         powered_by: "propulsé par"
         about: "À propos"
-    stats: Depuis le %user_creation%, vous avez lu %nb_archives% articles. Ce qui fait %per_day% par jour !
+    stats: "Depuis le %user_creation%, vous avez lu %nb_archives% articles. Ce qui fait %per_day% par jour !"
 
 config:
     page_title: "Configuration"
@@ -71,16 +71,16 @@ config:
             300_word: "Je lis environ 300 mots par minute"
             400_word: "Je lis environ 400 mots par minute"
         action_mark_as_read:
-            label: 'Où souhaitez-vous être redirigé après avoir marqué un article comme lu ?'
-            redirect_homepage: "À la page d'accueil"
-            redirect_current_page: 'À la page courante'
-        pocket_consumer_key_label: Clé d’authentification Pocket pour importer les données
-        android_configuration: Configurez votre application Android
-        help_theme: "L'affichage de wallabag est personnalisable. C'est ici que vous choisissez le thème que vous préférez."
-        help_items_per_page: "Vous pouvez définir le nombre d'articles affichés sur chaque page."
+            label: "Où souhaitez-vous être redirigé après avoir marqué un article comme lu ?"
+            redirect_homepage: "À la page daccueil"
+            redirect_current_page: "À la page courante"
+        pocket_consumer_key_label: "Clé d’authentification Pocket pour importer les données"
+        android_configuration: "Configurez votre application Android"
+        help_theme: "L’affichage de wallabag est personnalisable. C’est ici que vous choisissez le thème que vous préférez."
+        help_items_per_page: "Vous pouvez définir le nombre darticles affichés sur chaque page."
         help_reading_speed: "wallabag calcule une durée de lecture pour chaque article. Vous pouvez définir ici, grâce à cette liste déroulante, si vous lisez plus ou moins vite. wallabag recalculera la durée de lecture de chaque article."
-        help_language: "Vous pouvez définir la langue de l'interface de wallabag."
-        help_pocket_consumer_key: "Nécessaire pour l'import depuis Pocket. Vous pouvez le créer depuis votre compte Pocket."
+        help_language: "Vous pouvez définir la langue de linterface de wallabag."
+        help_pocket_consumer_key: "Nécessaire pour limport depuis Pocket. Vous pouvez le créer depuis votre compte Pocket."
     form_rss:
         description: "Les flux RSS fournis par wallabag vous permettent de lire vos articles sauvegardés dans votre lecteur de flux préféré. Pour pouvoir les utiliser, vous devez d’abord créer un jeton."
         token_label: "Jeton RSS"
@@ -100,18 +100,18 @@ config:
         twoFactorAuthentication_label: "Double authentification"
         help_twoFactorAuthentication: "Si vous activez 2FA, à chaque tentative de connexion à wallabag, vous recevrez un code par email."
         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'
+            title: "Supprimer mon compte (attention danger !)"
+            description: "Si vous confirmez la suppression de votre compte, TOUS les articles, TOUS les tags, TOUTES les annotations et votre compte seront DÉFINITIVEMENT supprimé (c’est IRRÉVERSIBLE). Vous serez ensuite déconnecté."
+            confirm: "Vous êtes vraiment sûr ? (C’EST IRRÉVERSIBLE)"
+            button: "Supprimer mon compte"
     reset:
-        title: Réinitialisation (attention danger !)
-        description: En cliquant sur les boutons ci-dessous vous avez la possibilité de supprimer certaines informations de votre compte. Attention, ces actions sont IRRÉVERSIBLES !
-        annotations: Supprimer TOUTES les annotations
-        tags: Supprimer TOUS les tags
-        entries: Supprimer TOUS les articles
-        archived: Supprimer TOUS les articles archivés
-        confirm: Êtes-vous vraiment vraiment sûr ? (C'EST IRRÉVERSIBLE)
+        title: "Réinitialisation (attention danger !)"
+        description: "En cliquant sur les boutons ci-dessous vous avez la possibilité de supprimer certaines informations de votre compte. Attention, ces actions sont IRRÉVERSIBLES !"
+        annotations: "Supprimer TOUTES les annotations"
+        tags: "Supprimer TOUS les tags"
+        entries: "Supprimer TOUS les articles"
+        archived: "Supprimer TOUS les articles archivés"
+        confirm: "Êtes-vous vraiment vraiment sûr ? (C’EST IRRÉVERSIBLE)"
     form_password:
         description: "Vous pouvez changer ici votre mot de passe. Le mot de passe doit contenir au moins 8 caractères."
         old_password_label: "Mot de passe actuel"
@@ -164,7 +164,7 @@ entry:
         archived: "Articles lus"
         filtered: "Articles filtrés"
         filtered_tags: "Articles filtrés par tags :"
-        filtered_search: 'Articles filtrés par recherche :'
+        filtered_search: "Articles filtrés par recherche :"
         untagged: "Article sans tag"
     list:
         number_on_the_page: "{0} Il n’y a pas d’article.|{1} Il y a un article.|]1,Inf[ Il y a %count% articles."
@@ -188,7 +188,7 @@ entry:
         preview_picture_label: "A une photo"
         preview_picture_help: "Photo"
         language_label: "Langue"
-        http_status_label: 'Statut HTTP'
+        http_status_label: "Statut HTTP"
         reading_time:
             label: "Durée de lecture en minutes"
             from: "de"
@@ -298,32 +298,32 @@ howto:
     bookmarklet:
         description: "Glissez et déposez ce lien dans votre barre de favoris :"
     shortcuts:
-        page_description: Voici les raccourcis disponibles dans wallabag.
-        shortcut: Raccourci
-        action: Action
-        all_pages_title: Raccourcis disponibles dans toutes les pages
-        go_unread: Afficher les articles non lus
-        go_starred: Afficher les articles favoris
-        go_archive:  Afficher les articles lus
-        go_all: Afficher tous les articles
-        go_tags: Afficher les tags
-        go_config: Aller à la configuration
-        go_import: Aller aux imports
-        go_developers: Aller à la section Développeurs
-        go_howto: Afficher l'aide (cette page !)
-        go_logout: Se déconnecter
-        list_title: Raccourcis disponibles dans les pages de liste
-        search: Afficher le formulaire de recherche
-        article_title: Raccourcis disponibles quand on affiche un article
-        open_original: Ouvrir l'URL originale de l'article
-        toggle_favorite: Changer le statut Favori de l'article
-        toggle_archive: Changer le status Lu de l'article
-        delete: Supprimer l'article
-        material_title: Raccourcis disponibles avec le thème Material uniquement
-        add_link: Ajouter un nouvel article
-        hide_form: Masquer le formulaire courant (recherche ou nouvel article)
-        arrows_navigation: Naviguer à travers les articles
-        open_article: Afficher l'article sélectionné
+        page_description: "Voici les raccourcis disponibles dans wallabag."
+        shortcut: "Raccourci"
+        action: "Action"
+        all_pages_title: "Raccourcis disponibles dans toutes les pages"
+        go_unread: "Afficher les articles non lus"
+        go_starred: "Afficher les articles favoris"
+        go_archive:  "Afficher les articles lus"
+        go_all: "Afficher tous les articles"
+        go_tags: "Afficher les tags"
+        go_config: "Aller à la configuration"
+        go_import: "Aller aux imports"
+        go_developers: "Aller à la section Développeurs"
+        go_howto: "Afficher l’aide (cette page !)"
+        go_logout: "Se déconnecter"
+        list_title: "Raccourcis disponibles dans les pages de liste"
+        search: "Afficher le formulaire de recherche"
+        article_title: "Raccourcis disponibles quand on affiche un article"
+        open_original: "Ouvrir l’URL originale de l’article"
+        toggle_favorite: "Changer le statut Favori de l’article"
+        toggle_archive: "Changer le status Lu de l’article"
+        delete: "Supprimer l’article"
+        material_title: "Raccourcis disponibles avec le thème Material uniquement"
+        add_link: "Ajouter un nouvel article"
+        hide_form: "Masquer le formulaire courant (recherche ou nouvel article)"
+        arrows_navigation: "Naviguer à travers les articles"
+        open_article: "Afficher l’article sélectionné"
 
 quickstart:
     page_title: "Pour bien débuter"
@@ -385,8 +385,8 @@ tag:
         number_on_the_page: "{0} Il n’y a pas de tag.|{1} Il y a un tag.|]1,Inf[ Il y a %count% tags."
         see_untagged_entries: "Voir les articles sans tag"
     new:
-        add: 'Ajouter'
-        placeholder: 'Vous pouvez ajouter plusieurs tags, séparés par une virgule.'
+        add: "Ajouter"
+        placeholder: "Vous pouvez ajouter plusieurs tags, séparés par une virgule."
 
 import:
     page_title: "Importer"
@@ -420,7 +420,7 @@ import:
         how_to: "Choisissez le fichier de votre export Readability et cliquez sur le bouton ci-dessous pour l’importer."
     worker:
         enabled: "Les imports sont asynchrones. Une fois l’import commencé un worker externe traitera les messages un par un. Le service activé est :"
-        download_images_warning: "Vous avez configuré le téléchagement des images pour vos articles. Combiné à l'import classique, cette opération peut être très très longue (voire échouer). Nous vous conseillons <strong>vivement</strong> d'activer les imports asynchrones."
+        download_images_warning: "Vous avez configuré le téléchagement des images pour vos articles. Combiné à l’import classique, cette opération peut être très très longue (voire échouer). Nous vous conseillons <strong>vivement</strong> d’activer les imports asynchrones."
     firefox:
         page_title: "Import > Firefox"
         description: "Cet outil va vous permettre d’importer tous vos marques-pages de Firefox. Ouvrez le panneau des marques-pages (Ctrl+Maj+O), puis dans « Importation et sauvegarde », choisissez « Sauvegarde… ». Vous allez récupérer un fichier .json. </p>"
@@ -489,16 +489,16 @@ developer:
         back: "Retour"
 
 user:
-    page_title: Gestion des utilisateurs
-    new_user: Créer un nouvel utilisateur
-    edit_user: Éditer un utilisateur existant
-    description: Ici vous pouvez gérer vos utilisateurs (création, mise à jour et suppression)
+    page_title: "Gestion des utilisateurs"
+    new_user: "Créer un nouvel utilisateur"
+    edit_user: "Éditer un utilisateur existant"
+    description: "Ici vous pouvez gérer vos utilisateurs (création, mise à jour et suppression)"
     list:
-        actions: Actions
-        edit_action: Éditer
-        yes: Oui
-        no: Non
-        create_new_one: Créer un nouvel utilisateur
+        actions: "Actions"
+        edit_action: "Éditer"
+        yes: "Oui"
+        no: "Non"
+        create_new_one: "Créer un nouvel utilisateur"
     form:
         username_label: "Nom d’utilisateur"
         name_label: "Nom"
@@ -513,9 +513,11 @@ user:
         delete: "Supprimer"
         delete_confirm: "Voulez-vous vraiment ?"
         back_to_list: "Revenir à la liste"
+    search:
+        placeholder: "Filtrer par nom d’utilisateur ou email"
 
 error:
-    page_title: Une erreur est survenue
+    page_title: "Une erreur est survenue"
 
 flashes:
     config:
@@ -528,10 +530,10 @@ flashes:
             tagging_rules_updated: "Règles mises à jour"
             tagging_rules_deleted: "Règle supprimée"
             rss_token_updated: "Jeton RSS mis à jour"
-            annotations_reset: Annotations supprimées
-            tags_reset: Tags supprimés
-            entries_reset: Articles supprimés
-            archived_reset: Articles archivés supprimés
+            annotations_reset: "Annotations supprimées"
+            tags_reset: "Tags supprimés"
+            entries_reset: "Articles supprimés"
+            archived_reset: "Articles archivés supprimés"
     entry:
         notice:
             entry_already_saved: "Article déjà sauvegardé le %date%"
@@ -563,6 +565,6 @@ flashes:
             client_deleted: "Client %name% supprimé"
     user:
         notice:
-            added: 'Utilisateur "%username%" ajouté'
-            updated: 'Utilisateur "%username%" mis à jour'
-            deleted: 'Utilisateur "%username%" supprimé'
+            added: "Utilisateur \"%username%\" ajouté"
+            updated: "Utilisateur \"%username%\" mis à jour"
+            deleted: "Utilisateur \"%username%\" supprimé"
index 374071ce0df95c8bf0a5a695699e7edc39e88cac..5a9605ff2c4ae85d58c870f927dc1d2ac68d3230 100644 (file)
@@ -513,6 +513,8 @@ user:
         # delete: Delete
         # delete_confirm: Are you sure?
         # back_to_list: Back to list
+    search:
+        # placeholder: Filter by username or email
 
 error:
     # page_title: An error occurred
index b01c611bc1f5183327cc90f6cfac943f21b04c29..942bc257863373a0ec5017625594e2c67addc657 100644 (file)
@@ -513,6 +513,8 @@ user:
         delete: 'Suprimir'
         delete_confirm: 'Sètz segur ?'
         back_to_list: 'Tornar a la lista'
+    search:
+        # placeholder: Filter by username or email
 
 error:
     page_title: Una error s'es produsida
index d76ac32818e5db110fa0d6ed440549d402d00363..fea90440a50a432f0bd844b8f080a2d91bb1fc92 100644 (file)
@@ -513,6 +513,8 @@ user:
         delete: Usuń
         delete_confirm: Jesteś pewien?
         back_to_list: Powrót do listy
+    search:
+        # placeholder: Filter by username or email
 
 error:
     page_title: Wystąpił błąd
index 98dfcd25788354a31c0b6c2b836ddc3ab2f2146e..c59991f89931263c6ebe02cfe78683a63155ae29 100644 (file)
@@ -513,6 +513,8 @@ user:
         delete: 'Apagar'
         delete_confirm: 'Tem certeza?'
         back_to_list: 'Voltar para a lista'
+    search:
+        # placeholder: Filter by username or email
 
 error:
     # page_title: An error occurred
index 8c07c13f3ab059b19e5bec3436e41b561bbdaf80..5846b7cc30076d3f8604b6e5a8b477c3e437cb27 100644 (file)
@@ -513,6 +513,8 @@ user:
         # delete: Delete
         # delete_confirm: Are you sure?
         # back_to_list: Back to list
+    search:
+        # placeholder: Filter by username or email
 
 error:
     # page_title: An error occurred
index bd21cb6743a1af6cc845f16d7f88bee438de3827..430fb96bcf129fe2f9c8f29f145f5413a55d8bad 100644 (file)
@@ -513,6 +513,8 @@ user:
         # delete: Delete
         # delete_confirm: Are you sure?
         # back_to_list: Back to list
+    search:
+        # placeholder: Filter by username or email
 
 error:
     # page_title: An error occurred
index 28d0171599a97db540077ce89d21d82bded2bd15..ce72837ad0e8807ca2d2ce98a8aa55accb805233 100644 (file)
@@ -15,10 +15,11 @@ class ImportCommand extends ContainerAwareCommand
         $this
             ->setName('wallabag:import')
             ->setDescription('Import entries from a JSON export')
-            ->addArgument('userId', InputArgument::REQUIRED, 'User ID to populate')
+            ->addArgument('username', InputArgument::REQUIRED, 'User to populate')
             ->addArgument('filepath', InputArgument::REQUIRED, 'Path to the JSON file')
             ->addOption('importer', null, InputArgument::OPTIONAL, 'The importer to use: v1, v2, instapaper, pinboard, readability, firefox or chrome', 'v1')
             ->addOption('markAsRead', null, InputArgument::OPTIONAL, 'Mark all entries as read', false)
+            ->addOption('useUserId', null, InputArgument::OPTIONAL, 'Use user id instead of username to find account', false)
         ;
     }
 
@@ -34,10 +35,14 @@ class ImportCommand extends ContainerAwareCommand
         // Turning off doctrine default logs queries for saving memory
         $em->getConnection()->getConfiguration()->setSQLLogger(null);
 
-        $user = $em->getRepository('WallabagUserBundle:User')->findOneById($input->getArgument('userId'));
+        if ($input->getOption('useUserId')) {
+            $user = $em->getRepository('WallabagUserBundle:User')->findOneById($input->getArgument('username'));
+        } else {
+            $user = $em->getRepository('WallabagUserBundle:User')->findOneByUsername($input->getArgument('username'));
+        }
 
         if (!is_object($user)) {
-            throw new Exception(sprintf('User with id "%s" not found', $input->getArgument('userId')));
+            throw new Exception(sprintf('User "%s" not found', $input->getArgument('username')));
         }
 
         switch ($input->getOption('importer')) {
index 92ee2b4162649fad67e3158ca91c105d13e58ff4..1c5c86d45501f41c987edba0efa48bf320f3fa93 100644 (file)
@@ -4,35 +4,21 @@ namespace Wallabag\UserBundle\Controller;
 
 use FOS\UserBundle\Event\UserEvent;
 use FOS\UserBundle\FOSUserEvents;
+use Pagerfanta\Adapter\DoctrineORMAdapter;
+use Pagerfanta\Exception\OutOfRangeCurrentPageException;
+use Pagerfanta\Pagerfanta;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Bundle\FrameworkBundle\Controller\Controller;
 use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
 use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
 use Wallabag\UserBundle\Entity\User;
-use Wallabag\CoreBundle\Entity\Config;
+use Wallabag\UserBundle\Form\SearchUserType;
 
 /**
  * User controller.
  */
 class ManageController extends Controller
 {
-    /**
-     * Lists all User entities.
-     *
-     * @Route("/", name="user_index")
-     * @Method("GET")
-     */
-    public function indexAction()
-    {
-        $em = $this->getDoctrine()->getManager();
-
-        $users = $em->getRepository('WallabagUserBundle:User')->findAll();
-
-        return $this->render('WallabagUserBundle:Manage:index.html.twig', array(
-            'users' => $users,
-        ));
-    }
-
     /**
      * Creates a new User entity.
      *
@@ -146,4 +132,49 @@ class ManageController extends Controller
             ->getForm()
         ;
     }
+
+    /**
+     * @param Request $request
+     * @param int     $page
+     *
+     * @Route("/list/{page}", name="user_index", defaults={"page" = 1})
+     *
+     * Default parameter for page is hardcoded (in duplication of the defaults from the Route)
+     * because this controller is also called inside the layout template without any page as argument
+     *
+     * @return \Symfony\Component\HttpFoundation\Response
+     */
+    public function searchFormAction(Request $request, $page = 1)
+    {
+        $em = $this->getDoctrine()->getManager();
+        $qb = $em->getRepository('WallabagUserBundle:User')->createQueryBuilder('u');
+
+        $form = $this->createForm(SearchUserType::class);
+        $form->handleRequest($request);
+
+        if ($form->isSubmitted() && $form->isValid()) {
+            $this->get('logger')->info('searching users');
+
+            $searchTerm = (isset($request->get('search_user')['term']) ? $request->get('search_user')['term'] : '');
+
+            $qb = $em->getRepository('WallabagUserBundle:User')->getQueryBuilderForSearch($searchTerm);
+        }
+
+        $pagerAdapter = new DoctrineORMAdapter($qb->getQuery(), true, false);
+        $pagerFanta = new Pagerfanta($pagerAdapter);
+        $pagerFanta->setMaxPerPage(50);
+
+        try {
+            $pagerFanta->setCurrentPage($page);
+        } catch (OutOfRangeCurrentPageException $e) {
+            if ($page > 1) {
+                return $this->redirect($this->generateUrl('user_index', ['page' => $pagerFanta->getNbPages()]), 302);
+            }
+        }
+
+        return $this->render('WallabagUserBundle:Manage:index.html.twig', [
+            'searchForm' => $form->createView(),
+            'users' => $pagerFanta,
+        ]);
+    }
 }
diff --git a/src/Wallabag/UserBundle/Form/SearchUserType.php b/src/Wallabag/UserBundle/Form/SearchUserType.php
new file mode 100644 (file)
index 0000000..9ce46ee
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+
+namespace Wallabag\UserBundle\Form;
+
+use Symfony\Component\Form\AbstractType;
+use Symfony\Component\Form\Extension\Core\Type\TextType;
+use Symfony\Component\Form\FormBuilderInterface;
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+class SearchUserType extends AbstractType
+{
+    public function buildForm(FormBuilderInterface $builder, array $options)
+    {
+        $builder
+            ->setMethod('GET')
+            ->add('term', TextType::class, [
+                'required' => true,
+                'label' => 'user.new.form_search.term_label',
+            ])
+        ;
+    }
+
+    public function configureOptions(OptionsResolver $resolver)
+    {
+        $resolver->setDefaults([
+            'csrf_protection' => false,
+        ]);
+    }
+}
index f913f52dd5beaf0fed96d38c940d230a98be07ba..6adbe329fb5519f9308be0aafc39968451ad8f88 100644 (file)
@@ -52,4 +52,17 @@ class UserRepository extends EntityRepository
             ->getQuery()
             ->getSingleScalarResult();
     }
+
+    /**
+     * Retrieves users filtered with a search term.
+     *
+     * @param string $term
+     *
+     * @return QueryBuilder
+     */
+    public function getQueryBuilderForSearch($term)
+    {
+        return $this->createQueryBuilder('u')
+            ->andWhere('lower(u.username) LIKE lower(:term) OR lower(u.email) LIKE lower(:term) OR lower(u.name) LIKE lower(:term)')->setParameter('term', '%'.$term.'%');
+    }
 }
index daba29e43ab54171d58b1c56654d074a45a30c7d..15002632cfaabaf5c9b353d55281e0aa2b86ba6a 100644 (file)
@@ -7,37 +7,60 @@
     <div class="row">
         <div class="col s12">
             <div class="card-panel">
+                {% if users.getNbPages > 1 %}
+                    {{ pagerfanta(users, 'twitter_bootstrap_translated', {'proximity': 1}) }}
+                {% endif %}
                 <div class="row">
-                    <div class="input-field col s12">
+                    <div class="col s6">
                         <p class="help">{{ 'user.description'|trans|raw }}</p>
+                    </div>
+                    <div class="col s6">
+                        <div class="input-field">
+                            <form name="search_users" method="GET" action="{{ path('user_index')}}">
+                                {% if form_errors(searchForm) %}
+                                    <span class="black-text">{{ form_errors(searchForm) }}</span>
+                                {% endif %}
+
+                                {% if form_errors(searchForm.term) %}
+                                    <span class="black-text">{{ form_errors(searchForm.term) }}</span>
+                                {% endif %}
 
-                        <table class="bordered">
-                            <thead>
-                                <tr>
-                                    <th>{{ 'user.form.username_label'|trans }}</th>
-                                    <th>{{ 'user.form.email_label'|trans }}</th>
-                                    <th>{{ 'user.form.last_login_label'|trans }}</th>
-                                    <th>{{ 'user.list.actions'|trans }}</th>
-                                </tr>
-                            </thead>
-                            <tbody>
-                            {% for user in users %}
-                                <tr>
-                                    <td>{{ user.username }}</td>
-                                    <td>{{ user.email }}</td>
-                                    <td>{% if user.lastLogin %}{{ user.lastLogin|date('Y-m-d H:i:s') }}{% endif %}</td>
-                                    <td>
-                                        <a href="{{ path('user_edit', { 'id': user.id }) }}">{{ 'user.list.edit_action'|trans }}</a>
-                                    </td>
-                                </tr>
-                            {% endfor %}
-                            </tbody>
-                        </table>
-                        <br />
-                        <p>
-                            <a href="{{ path('user_new') }}" class="waves-effect waves-light btn">{{ 'user.list.create_new_one'|trans }}</a>
-                        </p>
+                                {{ form_widget(searchForm.term, { 'attr': {'autocomplete': 'off', 'placeholder': 'user.search.placeholder'} }) }}
+
+                                {{ form_rest(searchForm) }}
+                            </form>
+                        </div>
                     </div>
+
+                    <table class="bordered">
+                        <thead>
+                            <tr>
+                                <th>{{ 'user.form.username_label'|trans }}</th>
+                                <th>{{ 'user.form.email_label'|trans }}</th>
+                                <th>{{ 'user.form.last_login_label'|trans }}</th>
+                                <th>{{ 'user.list.actions'|trans }}</th>
+                            </tr>
+                        </thead>
+                        <tbody>
+                        {% for user in users %}
+                            <tr>
+                                <td>{{ user.username }}</td>
+                                <td>{{ user.email }}</td>
+                                <td>{% if user.lastLogin %}{{ user.lastLogin|date('Y-m-d H:i:s') }}{% endif %}</td>
+                                <td>
+                                    <a href="{{ path('user_edit', { 'id': user.id }) }}">{{ 'user.list.edit_action'|trans }}</a>
+                                </td>
+                            </tr>
+                        {% endfor %}
+                        </tbody>
+                    </table>
+                    <br />
+                    <p>
+                        <a href="{{ path('user_new') }}" class="waves-effect waves-light btn">{{ 'user.list.create_new_one'|trans }}</a>
+                    </p>
+                    {% if users.getNbPages > 1 %}
+                        {{ pagerfanta(users, 'twitter_bootstrap_translated', {'proximity': 1}) }}
+                    {% endif %}
                 </div>
             </div>
         </div>
index e6ffd6641a319f6575f2ce41fbbd6729e09ecc1f..362c269b40544bf7541ee12720a354d23e201c73 100644 (file)
@@ -767,19 +767,67 @@ class EntryRestControllerTest extends WallabagApiTestCase
         ];
 
         $this->client->request('DELETE', '/api/entries/tags/list?list='.json_encode($list));
+    }
+
+    public function testPostEntriesListAction()
+    {
+        $list = [
+            'http://www.lemonde.fr/musiques/article/2017/04/23/loin-de-la-politique-le-printemps-de-bourges-retombe-en-enfance_5115862_1654986.html',
+            'http://0.0.0.0/entry2',
+        ];
+
+        $this->client->request('POST', '/api/entries/lists?urls='.json_encode($list));
 
         $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
 
         $content = json_decode($this->client->getResponse()->getContent(), true);
 
         $this->assertInternalType('int', $content[0]['entry']);
-        $this->assertEquals('http://0.0.0.0/entry4', $content[0]['url']);
+        $this->assertEquals('http://www.lemonde.fr/musiques/article/2017/04/23/loin-de-la-politique-le-printemps-de-bourges-retombe-en-enfance_5115862_1654986.html', $content[0]['url']);
 
-        $entry = $this->client->getContainer()->get('doctrine.orm.entity_manager')
-            ->getRepository('WallabagCoreBundle:Entry')
-            ->findByUrlAndUserId('http://0.0.0.0/entry4', 1);
+        $this->assertInternalType('int', $content[1]['entry']);
+        $this->assertEquals('http://0.0.0.0/entry2', $content[1]['url']);
+    }
 
-        $tags = $entry->getTags();
-        $this->assertCount(2, $tags);
+    public function testDeleteEntriesListAction()
+    {
+        $list = [
+            'http://www.lemonde.fr/musiques/article/2017/04/23/loin-de-la-politique-le-printemps-de-bourges-retombe-en-enfance_5115862_1654986.html',
+            'http://0.0.0.0/entry3',
+        ];
+
+        $this->client->request('DELETE', '/api/entries/list?urls='.json_encode($list));
+
+        $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
+
+        $content = json_decode($this->client->getResponse()->getContent(), true);
+
+        $this->assertTrue($content[0]['entry']);
+        $this->assertEquals('http://www.lemonde.fr/musiques/article/2017/04/23/loin-de-la-politique-le-printemps-de-bourges-retombe-en-enfance_5115862_1654986.html', $content[0]['url']);
+
+        $this->assertFalse($content[1]['entry']);
+        $this->assertEquals('http://0.0.0.0/entry3', $content[1]['url']);
+    }
+
+    public function testLimitBulkAction()
+    {
+        $list = [
+            'http://0.0.0.0/entry1',
+            'http://0.0.0.0/entry1',
+            'http://0.0.0.0/entry1',
+            'http://0.0.0.0/entry1',
+            'http://0.0.0.0/entry1',
+            'http://0.0.0.0/entry1',
+            'http://0.0.0.0/entry1',
+            'http://0.0.0.0/entry1',
+            'http://0.0.0.0/entry1',
+            'http://0.0.0.0/entry1',
+            'http://0.0.0.0/entry1',
+        ];
+
+        $this->client->request('POST', '/api/entries/lists?urls='.json_encode($list));
+
+        $this->assertEquals(400, $this->client->getResponse()->getStatusCode());
+        $this->assertContains('API limit reached', $this->client->getResponse()->getContent());
     }
 }
diff --git a/tests/Wallabag/CoreBundle/Command/CleanDuplicatesCommandTest.php b/tests/Wallabag/CoreBundle/Command/CleanDuplicatesCommandTest.php
new file mode 100644 (file)
index 0000000..e6e57f3
--- /dev/null
@@ -0,0 +1,108 @@
+<?php
+
+namespace Tests\Wallabag\CoreBundle\Command;
+
+use Symfony\Bundle\FrameworkBundle\Console\Application;
+use Symfony\Component\Console\Tester\CommandTester;
+use Wallabag\CoreBundle\Command\CleanDuplicatesCommand;
+use Tests\Wallabag\CoreBundle\WallabagCoreTestCase;
+use Wallabag\CoreBundle\Entity\Entry;
+
+class CleanDuplicatesCommandTest extends WallabagCoreTestCase
+{
+    public function testRunCleanDuplicates()
+    {
+        $application = new Application($this->getClient()->getKernel());
+        $application->add(new CleanDuplicatesCommand());
+
+        $command = $application->find('wallabag:clean-duplicates');
+
+        $tester = new CommandTester($command);
+        $tester->execute([
+            'command' => $command->getName(),
+        ]);
+
+        $this->assertContains('Cleaning through 3 user accounts', $tester->getDisplay());
+        $this->assertContains('Finished cleaning. 0 duplicates found in total', $tester->getDisplay());
+    }
+
+    public function testRunCleanDuplicatesCommandWithBadUsername()
+    {
+        $application = new Application($this->getClient()->getKernel());
+        $application->add(new CleanDuplicatesCommand());
+
+        $command = $application->find('wallabag:clean-duplicates');
+
+        $tester = new CommandTester($command);
+        $tester->execute([
+            'command' => $command->getName(),
+            'username' => 'unknown',
+        ]);
+
+        $this->assertContains('User "unknown" not found', $tester->getDisplay());
+    }
+
+    public function testRunCleanDuplicatesCommandForUser()
+    {
+        $application = new Application($this->getClient()->getKernel());
+        $application->add(new CleanDuplicatesCommand());
+
+        $command = $application->find('wallabag:clean-duplicates');
+
+        $tester = new CommandTester($command);
+        $tester->execute([
+            'command' => $command->getName(),
+            'username' => 'admin',
+        ]);
+
+        $this->assertContains('Cleaned 0 duplicates for user admin', $tester->getDisplay());
+    }
+
+    public function testDuplicate()
+    {
+        $url = 'http://www.lemonde.fr/sport/visuel/2017/05/05/rondelle-prison-blanchissage-comprendre-le-hockey-sur-glace_5122587_3242.html';
+        $client = $this->getClient();
+        $em = $client->getContainer()->get('doctrine.orm.entity_manager');
+
+        $this->logInAs('admin');
+
+        $nbEntries = $em->getRepository('WallabagCoreBundle:Entry')->findAllByUrlAndUserId($url, $this->getLoggedInUserId());
+        $this->assertCount(0, $nbEntries);
+
+        $user = $em->getRepository('WallabagUserBundle:User')->findOneById($this->getLoggedInUserId());
+
+        $entry1 = new Entry($user);
+        $entry1->setUrl($url);
+
+        $entry2 = new Entry($user);
+        $entry2->setUrl($url);
+
+        $em->persist($entry1);
+        $em->persist($entry2);
+
+        $em->flush();
+
+        $nbEntries = $em->getRepository('WallabagCoreBundle:Entry')->findAllByUrlAndUserId($url, $this->getLoggedInUserId());
+        $this->assertCount(2, $nbEntries);
+
+        $application = new Application($this->getClient()->getKernel());
+        $application->add(new CleanDuplicatesCommand());
+
+        $command = $application->find('wallabag:clean-duplicates');
+
+        $tester = new CommandTester($command);
+        $tester->execute([
+            'command' => $command->getName(),
+            'username' => 'admin',
+        ]);
+
+        $this->assertContains('Cleaned 1 duplicates for user admin', $tester->getDisplay());
+
+        $nbEntries = $em->getRepository('WallabagCoreBundle:Entry')->findAllByUrlAndUserId($url, $this->getLoggedInUserId());
+        $this->assertCount(1, $nbEntries);
+
+        $query = $em->createQuery('DELETE FROM Wallabag\CoreBundle\Entity\Entry e WHERE e.url = :url');
+        $query->setParameter('url', $url);
+        $query->execute();
+    }
+}
index 6798c5d71dc9692f9cad30fd0f2bd5df5bb1d9f5..b21f3318554dc7210892185d6fb35cea611c4baf 100644 (file)
@@ -70,7 +70,7 @@ class ExportCommandTest extends WallabagCoreTestCase
         $tester->execute([
             'command' => $command->getName(),
             'username' => 'admin',
-            'filepath' => 'specialexport.json'
+            'filepath' => 'specialexport.json',
         ]);
 
         $this->assertFileExists('specialexport.json');
index 1bfd41d50817966b1d70927c0809d6c75870c8cf..122a87d400239c53f6c83ab9ec790ee02154824c 100644 (file)
@@ -87,6 +87,7 @@ class InstallCommandTest extends WallabagCoreTestCase
         $this->assertContains('Setting up database.', $tester->getDisplay());
         $this->assertContains('Administration setup.', $tester->getDisplay());
         $this->assertContains('Config setup.', $tester->getDisplay());
+        $this->assertContains('Run migrations.', $tester->getDisplay());
     }
 
     public function testRunInstallCommandWithReset()
@@ -115,12 +116,13 @@ class InstallCommandTest extends WallabagCoreTestCase
 
         $this->assertContains('Checking system requirements.', $tester->getDisplay());
         $this->assertContains('Setting up database.', $tester->getDisplay());
-        $this->assertContains('Droping database, creating database and schema, clearing the cache', $tester->getDisplay());
+        $this->assertContains('Dropping database, creating database and schema, clearing the cache', $tester->getDisplay());
         $this->assertContains('Administration setup.', $tester->getDisplay());
         $this->assertContains('Config setup.', $tester->getDisplay());
+        $this->assertContains('Run migrations.', $tester->getDisplay());
 
         // we force to reset everything
-        $this->assertContains('Droping database, creating database and schema, clearing the cache', $tester->getDisplay());
+        $this->assertContains('Dropping database, creating database and schema, clearing the cache', $tester->getDisplay());
     }
 
     public function testRunInstallCommandWithDatabaseRemoved()
@@ -168,6 +170,7 @@ class InstallCommandTest extends WallabagCoreTestCase
         $this->assertContains('Setting up database.', $tester->getDisplay());
         $this->assertContains('Administration setup.', $tester->getDisplay());
         $this->assertContains('Config setup.', $tester->getDisplay());
+        $this->assertContains('Run migrations.', $tester->getDisplay());
 
         // the current database doesn't already exist
         $this->assertContains('Creating database and schema, clearing the cache', $tester->getDisplay());
@@ -205,8 +208,9 @@ class InstallCommandTest extends WallabagCoreTestCase
         $this->assertContains('Setting up database.', $tester->getDisplay());
         $this->assertContains('Administration setup.', $tester->getDisplay());
         $this->assertContains('Config setup.', $tester->getDisplay());
+        $this->assertContains('Run migrations.', $tester->getDisplay());
 
-        $this->assertContains('Droping schema and creating schema', $tester->getDisplay());
+        $this->assertContains('Dropping schema and creating schema', $tester->getDisplay());
     }
 
     public function testRunInstallCommandChooseNothing()
@@ -259,6 +263,7 @@ class InstallCommandTest extends WallabagCoreTestCase
         $this->assertContains('Setting up database.', $tester->getDisplay());
         $this->assertContains('Administration setup.', $tester->getDisplay());
         $this->assertContains('Config setup.', $tester->getDisplay());
+        $this->assertContains('Run migrations.', $tester->getDisplay());
 
         $this->assertContains('Creating schema', $tester->getDisplay());
     }
@@ -291,5 +296,6 @@ class InstallCommandTest extends WallabagCoreTestCase
         $this->assertContains('Setting up database.', $tester->getDisplay());
         $this->assertContains('Administration setup.', $tester->getDisplay());
         $this->assertContains('Config setup.', $tester->getDisplay());
+        $this->assertContains('Run migrations.', $tester->getDisplay());
     }
 }
index 5956b502fb68cf4fa6372e3382cb7808cd2f86ef..8abb1bbba2fe5dec35f066a0b8716c8128baa52e 100644 (file)
@@ -111,7 +111,7 @@ class ContentProxyTest extends \PHPUnit_Framework_TestCase
 
         $this->assertEquals('http://domain.io', $entry->getUrl());
         $this->assertEquals('my title', $entry->getTitle());
-        $this->assertEquals($this->fetchingErrorMessage . '<p><i>But we found a short description: </i></p>desc', $entry->getContent());
+        $this->assertEquals($this->fetchingErrorMessage.'<p><i>But we found a short description: </i></p>desc', $entry->getContent());
         $this->assertEmpty($entry->getPreviewPicture());
         $this->assertEmpty($entry->getLanguage());
         $this->assertEmpty($entry->getHttpStatus());
index 7be1eb18f6f4a0e682966b0ac1f3639d04b859ee..7043c3450b73c63d267057c20e13b160840767e4 100644 (file)
@@ -10,7 +10,7 @@ use Tests\Wallabag\CoreBundle\WallabagCoreTestCase;
 class ImportCommandTest extends WallabagCoreTestCase
 {
     /**
-     * @expectedException Symfony\Component\Console\Exception\RuntimeException
+     * @expectedException \Symfony\Component\Console\Exception\RuntimeException
      * @expectedExceptionMessage Not enough arguments
      */
     public function testRunImportCommandWithoutArguments()
@@ -27,7 +27,7 @@ class ImportCommandTest extends WallabagCoreTestCase
     }
 
     /**
-     * @expectedException Symfony\Component\Config\Definition\Exception\Exception
+     * @expectedException \Symfony\Component\Config\Definition\Exception\Exception
      * @expectedExceptionMessage not found
      */
     public function testRunImportCommandWithoutFilepath()
@@ -40,16 +40,15 @@ class ImportCommandTest extends WallabagCoreTestCase
         $tester = new CommandTester($command);
         $tester->execute([
             'command' => $command->getName(),
-            'userId' => 1,
+            'username' => 'admin',
             'filepath' => 1,
         ]);
     }
 
     /**
-     * @expectedException Symfony\Component\Config\Definition\Exception\Exception
-     * @expectedExceptionMessage User with id
+     * @expectedException \Doctrine\ORM\NoResultException
      */
-    public function testRunImportCommandWithoutUserId()
+    public function testRunImportCommandWithWrongUsername()
     {
         $application = new Application($this->getClient()->getKernel());
         $application->add(new ImportCommand());
@@ -59,7 +58,7 @@ class ImportCommandTest extends WallabagCoreTestCase
         $tester = new CommandTester($command);
         $tester->execute([
             'command' => $command->getName(),
-            'userId' => 0,
+            'username' => 'random',
             'filepath' => './',
         ]);
     }
@@ -74,7 +73,7 @@ class ImportCommandTest extends WallabagCoreTestCase
         $tester = new CommandTester($command);
         $tester->execute([
             'command' => $command->getName(),
-            'userId' => 1,
+            'username' => 'admin',
             'filepath' => $application->getKernel()->getContainer()->getParameter('kernel.root_dir').'/../tests/Wallabag/ImportBundle/fixtures/wallabag-v2-read.json',
             '--importer' => 'v2',
         ]);
@@ -82,4 +81,20 @@ class ImportCommandTest extends WallabagCoreTestCase
         $this->assertContains('imported', $tester->getDisplay());
         $this->assertContains('already saved', $tester->getDisplay());
     }
+
+    public function testRunImportCommandWithUserId()
+    {
+        $application = new Application($this->getClient()->getKernel());
+        $application->add(new ImportCommand());
+
+        $command = $application->find('wallabag:import');
+
+        $tester = new CommandTester($command);
+        $tester->execute([
+            'command' => $command->getName(),
+            'username' => 1,
+            'filepath' => $application->getKernel()->getContainer()->getParameter('kernel.root_dir').'/../tests/Wallabag/ImportBundle/fixtures/wallabag-v2-read.json',
+            '--useUserId' => true,
+        ]);
+    }
 }
index 4faddfc4da7a6e81616a285e1dc3d882f3c61161..44b9a0301c51e11b4ba255c6b0c13a266f6f0af8 100644 (file)
@@ -10,7 +10,7 @@ class ManageControllerTest extends WallabagCoreTestCase
     {
         $client = $this->getClient();
 
-        $client->request('GET', '/users/');
+        $client->request('GET', '/users/list');
 
         $this->assertEquals(302, $client->getResponse()->getStatusCode());
         $this->assertContains('login', $client->getResponse()->headers->get('location'));
@@ -22,7 +22,7 @@ class ManageControllerTest extends WallabagCoreTestCase
         $client = $this->getClient();
 
         // Create a new user in the database
-        $crawler = $client->request('GET', '/users/');
+        $crawler = $client->request('GET', '/users/list');
         $this->assertEquals(200, $client->getResponse()->getStatusCode(), 'Unexpected HTTP status code for GET /users/');
         $crawler = $client->click($crawler->selectLink('user.list.create_new_one')->link());
 
@@ -36,7 +36,7 @@ class ManageControllerTest extends WallabagCoreTestCase
 
         $client->submit($form);
         $client->followRedirect();
-        $crawler = $client->request('GET', '/users/');
+        $crawler = $client->request('GET', '/users/list');
 
         // Check data in the show view
         $this->assertGreaterThan(0, $crawler->filter('td:contains("test_user")')->count(), 'Missing element td:contains("test_user")');
@@ -57,7 +57,7 @@ class ManageControllerTest extends WallabagCoreTestCase
         // Check the element contains an attribute with value equals "Foo User"
         $this->assertGreaterThan(0, $crawler->filter('[value="Foo User"]')->count(), 'Missing element [value="Foo User"]');
 
-        $crawler = $client->request('GET', '/users/');
+        $crawler = $client->request('GET', '/users/list');
         $crawler = $client->click($crawler->selectLink('user.list.edit_action')->last()->link());
 
         // Delete the user
@@ -78,4 +78,22 @@ class ManageControllerTest extends WallabagCoreTestCase
 
         $this->assertEquals('disabled', $disabled[0]);
     }
+
+    public function testUserSearch()
+    {
+        $this->logInAs('admin');
+        $client = $this->getClient();
+
+        // Search on unread list
+        $crawler = $client->request('GET', '/users/list');
+
+        $form = $crawler->filter('form[name=search_users]')->form();
+        $data = [
+            'search_user[term]' => 'admin',
+        ];
+
+        $crawler = $client->submit($form, $data);
+
+        $this->assertCount(2, $crawler->filter('tr')); // 1 result + table header
+    }
 }