diff options
author | Jérémy Benoist <j0k3r@users.noreply.github.com> | 2017-05-05 17:42:18 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-05-05 17:42:18 +0200 |
commit | ab742ee9c69f8cf6e6295d6044e05accffc5551d (patch) | |
tree | 503f54518cd9df45434903714eff2a5ab71c8a7c | |
parent | 69803049688179e1b03ef424dec91f1b9a4f9e91 (diff) | |
parent | 4eeb29ff784934fa879dd87999e07c4c7626af8c (diff) | |
download | wallabag-ab742ee9c69f8cf6e6295d6044e05accffc5551d.tar.gz wallabag-ab742ee9c69f8cf6e6295d6044e05accffc5551d.tar.zst wallabag-ab742ee9c69f8cf6e6295d6044e05accffc5551d.zip |
Merge pull request #2920 from wallabag/cleanduplicatescommand
Clean Duplicates Command
-rw-r--r-- | docs/en/developer/console_commands.rst | 30 | ||||
-rw-r--r-- | docs/fr/developer/console_commands.rst | 30 | ||||
-rw-r--r-- | src/Wallabag/CoreBundle/Command/CleanDuplicatesCommand.php | 119 | ||||
-rw-r--r-- | src/Wallabag/CoreBundle/Repository/EntryRepository.php | 30 | ||||
-rw-r--r-- | tests/Wallabag/CoreBundle/Command/CleanDuplicatesCommandTest.php | 108 | ||||
-rw-r--r-- | tests/Wallabag/CoreBundle/Command/ExportCommandTest.php | 2 | ||||
-rw-r--r-- | tests/Wallabag/CoreBundle/Helper/ContentProxyTest.php | 2 |
7 files changed, 319 insertions, 2 deletions
diff --git a/docs/en/developer/console_commands.rst b/docs/en/developer/console_commands.rst new file mode 100644 index 00000000..85a8a092 --- /dev/null +++ b/docs/en/developer/console_commands.rst | |||
@@ -0,0 +1,30 @@ | |||
1 | Console Commands | ||
2 | ================ | ||
3 | |||
4 | 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. | ||
5 | |||
6 | Each command has a help accessible through `bin/console help %command%`. | ||
7 | |||
8 | .. note:: | ||
9 | |||
10 | If you're in a production environment, remember to add `-e prod` to each command. | ||
11 | |||
12 | Notable commands | ||
13 | ---------------- | ||
14 | |||
15 | * `assets:install`: May be helpful if assets are missing. | ||
16 | * `cache:clear`: should be run after each update (included in `make update`). | ||
17 | * `doctrine:migrations:status`: Output the status of your database migrations. | ||
18 | * `fos:user:activate`: Manually activate an user. | ||
19 | * `fos:user:change-password`: Change a password for an user. | ||
20 | * `fos:user:create`: Create an user. | ||
21 | * `fos:user:deactivate`: Deactivate an user (not deleted). | ||
22 | * `fos:user:demote`: Removes a role from an user, typically admin rights. | ||
23 | * `fos:user:promote`: Adds a role to an user, typically admin rights. | ||
24 | * `rabbitmq:*`: May be useful if you're using RabbitMQ. | ||
25 | * `wallabag:clean-duplicates`: Removes all entry duplicates for one user or all users | ||
26 | * `wallabag:export`: Exports all entries for an user. You can choose the output path of the file. | ||
27 | * `wallabag:import`: Import entries to different formats to an user account. | ||
28 | * `wallabag:import:redis-worker`: Useful if you use Redis. | ||
29 | * `wallabag:install`: (re)Install wallabag | ||
30 | * `wallabag:tag:all`: Tag all entries for an user using his/her tagging rules. | ||
diff --git a/docs/fr/developer/console_commands.rst b/docs/fr/developer/console_commands.rst new file mode 100644 index 00000000..1b222b32 --- /dev/null +++ b/docs/fr/developer/console_commands.rst | |||
@@ -0,0 +1,30 @@ | |||
1 | Actions en ligne de commande | ||
2 | ============================ | ||
3 | |||
4 | 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. | ||
5 | |||
6 | Chaque commande a une aide correspondante accessible via `bin/console help %command%`. | ||
7 | |||
8 | .. note:: | ||
9 | |||
10 | Si vous êtes dans un environnement de production, souvenez-vous d'ajouter `-e prod` à chaque commande. | ||
11 | |||
12 | Commandes notables | ||
13 | ------------------ | ||
14 | |||
15 | * `assets:install`: Peut-être utile si les *assets* sont manquants. | ||
16 | * `cache:clear`: doit être exécuté après chaque mise à jour (appelé dans `make update`). | ||
17 | * `doctrine:migrations:status`: Montre le statut de vos migrations de vos bases de données. | ||
18 | * `fos:user:activate`: Activer manuellement un utilisateur. | ||
19 | * `fos:user:change-password`: Changer le mot de passe pour un utilisateur. | ||
20 | * `fos:user:create`: Créer un utilisateur. | ||
21 | * `fos:user:deactivate`: Désactiver un utilisateur (non supprimé). | ||
22 | * `fos:user:demote`: Supprimer un rôle d'un utilisateur, typiquement les droits d'administration. | ||
23 | * `fos:user:promote`: Ajoute un rôle à un utilisateur, typiquement les droits d'administration. | ||
24 | * `rabbitmq:*`: Peut-être utile si vous utilisez RabbitMQ. | ||
25 | * `wallabag:clean-duplicates`: Supprime tous les articles dupliqués pour un utilisateur ou bien tous. | ||
26 | * `wallabag:export`: Exporte tous les articles pour un utilisateur. Vous pouvez choisir le chemin du fichier exporté. | ||
27 | * `wallabag:import`: Importe les articles en différents formats dans un compte utilisateur. | ||
28 | * `wallabag:import:redis-worker`: Utile si vous utilisez Redis. | ||
29 | * `wallabag:install`: (ré)Installer wallabag | ||
30 | * `wallabag:tag:all`: Tagger tous les articles pour un utilisateur ou une utilisatrice en utilisant ses règles de tags automatiques. | ||
diff --git a/src/Wallabag/CoreBundle/Command/CleanDuplicatesCommand.php b/src/Wallabag/CoreBundle/Command/CleanDuplicatesCommand.php new file mode 100644 index 00000000..65f35d8e --- /dev/null +++ b/src/Wallabag/CoreBundle/Command/CleanDuplicatesCommand.php | |||
@@ -0,0 +1,119 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Wallabag\CoreBundle\Command; | ||
4 | |||
5 | use Doctrine\ORM\NoResultException; | ||
6 | use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand; | ||
7 | use Symfony\Component\Console\Input\InputArgument; | ||
8 | use Symfony\Component\Console\Input\InputInterface; | ||
9 | use Symfony\Component\Console\Output\OutputInterface; | ||
10 | use Wallabag\CoreBundle\Entity\Entry; | ||
11 | use Wallabag\UserBundle\Entity\User; | ||
12 | |||
13 | class CleanDuplicatesCommand extends ContainerAwareCommand | ||
14 | { | ||
15 | /** @var OutputInterface */ | ||
16 | protected $output; | ||
17 | |||
18 | protected $duplicates = 0; | ||
19 | |||
20 | protected function configure() | ||
21 | { | ||
22 | $this | ||
23 | ->setName('wallabag:clean-duplicates') | ||
24 | ->setDescription('Cleans the database for duplicates') | ||
25 | ->setHelp('This command helps you to clean your articles list in case of duplicates') | ||
26 | ->addArgument( | ||
27 | 'username', | ||
28 | InputArgument::OPTIONAL, | ||
29 | 'User to clean' | ||
30 | ); | ||
31 | } | ||
32 | |||
33 | protected function execute(InputInterface $input, OutputInterface $output) | ||
34 | { | ||
35 | $this->output = $output; | ||
36 | |||
37 | $username = $input->getArgument('username'); | ||
38 | |||
39 | if ($username) { | ||
40 | try { | ||
41 | $user = $this->getUser($username); | ||
42 | $this->cleanDuplicates($user); | ||
43 | } catch (NoResultException $e) { | ||
44 | $output->writeln(sprintf('<error>User "%s" not found.</error>', $username)); | ||
45 | |||
46 | return 1; | ||
47 | } | ||
48 | } else { | ||
49 | $users = $this->getDoctrine()->getRepository('WallabagUserBundle:User')->findAll(); | ||
50 | |||
51 | $output->writeln(sprintf('Cleaning through %d user accounts', count($users))); | ||
52 | |||
53 | foreach ($users as $user) { | ||
54 | $output->writeln(sprintf('Processing user %s', $user->getUsername())); | ||
55 | $this->cleanDuplicates($user); | ||
56 | } | ||
57 | $output->writeln(sprintf('Finished cleaning. %d duplicates found in total', $this->duplicates)); | ||
58 | } | ||
59 | |||
60 | return 0; | ||
61 | } | ||
62 | |||
63 | /** | ||
64 | * @param User $user | ||
65 | */ | ||
66 | private function cleanDuplicates(User $user) | ||
67 | { | ||
68 | $em = $this->getContainer()->get('doctrine.orm.entity_manager'); | ||
69 | $repo = $this->getDoctrine()->getRepository('WallabagCoreBundle:Entry'); | ||
70 | |||
71 | $entries = $repo->getAllEntriesIdAndUrl($user->getId()); | ||
72 | |||
73 | $duplicatesCount = 0; | ||
74 | $urls = []; | ||
75 | foreach ($entries as $entry) { | ||
76 | $url = $this->similarUrl($entry['url']); | ||
77 | |||
78 | /* @var $entry Entry */ | ||
79 | if (in_array($url, $urls)) { | ||
80 | ++$duplicatesCount; | ||
81 | |||
82 | $em->remove($repo->find($entry['id'])); | ||
83 | $em->flush(); // Flushing at the end of the loop would require the instance not being online | ||
84 | } else { | ||
85 | $urls[] = $entry['url']; | ||
86 | } | ||
87 | } | ||
88 | |||
89 | $this->duplicates += $duplicatesCount; | ||
90 | |||
91 | $this->output->writeln(sprintf('Cleaned %d duplicates for user %s', $duplicatesCount, $user->getUserName())); | ||
92 | } | ||
93 | |||
94 | private function similarUrl($url) | ||
95 | { | ||
96 | if (in_array(substr($url, -1), ['/', '#'])) { // get rid of "/" and "#" and the end of urls | ||
97 | return substr($url, 0, strlen($url)); | ||
98 | } | ||
99 | |||
100 | return $url; | ||
101 | } | ||
102 | |||
103 | /** | ||
104 | * Fetches a user from its username. | ||
105 | * | ||
106 | * @param string $username | ||
107 | * | ||
108 | * @return \Wallabag\UserBundle\Entity\User | ||
109 | */ | ||
110 | private function getUser($username) | ||
111 | { | ||
112 | return $this->getDoctrine()->getRepository('WallabagUserBundle:User')->findOneByUserName($username); | ||
113 | } | ||
114 | |||
115 | private function getDoctrine() | ||
116 | { | ||
117 | return $this->getContainer()->get('doctrine'); | ||
118 | } | ||
119 | } | ||
diff --git a/src/Wallabag/CoreBundle/Repository/EntryRepository.php b/src/Wallabag/CoreBundle/Repository/EntryRepository.php index 1f22e901..6972e974 100644 --- a/src/Wallabag/CoreBundle/Repository/EntryRepository.php +++ b/src/Wallabag/CoreBundle/Repository/EntryRepository.php | |||
@@ -379,4 +379,34 @@ class EntryRepository extends EntityRepository | |||
379 | ->setParameter('userId', $userId) | 379 | ->setParameter('userId', $userId) |
380 | ->execute(); | 380 | ->execute(); |
381 | } | 381 | } |
382 | |||
383 | /** | ||
384 | * Get id and url from all entries | ||
385 | * Used for the clean-duplicates command. | ||
386 | */ | ||
387 | public function getAllEntriesIdAndUrl($userId) | ||
388 | { | ||
389 | $qb = $this->createQueryBuilder('e') | ||
390 | ->select('e.id, e.url') | ||
391 | ->where('e.user = :userid')->setParameter(':userid', $userId); | ||
392 | |||
393 | return $qb->getQuery()->getArrayResult(); | ||
394 | } | ||
395 | |||
396 | /** | ||
397 | * Find all entries by url and owner. | ||
398 | * | ||
399 | * @param $url | ||
400 | * @param $userId | ||
401 | * | ||
402 | * @return array | ||
403 | */ | ||
404 | public function findAllByUrlAndUserId($url, $userId) | ||
405 | { | ||
406 | return $this->createQueryBuilder('e') | ||
407 | ->where('e.url = :url')->setParameter('url', urldecode($url)) | ||
408 | ->andWhere('e.user = :user_id')->setParameter('user_id', $userId) | ||
409 | ->getQuery() | ||
410 | ->getResult(); | ||
411 | } | ||
382 | } | 412 | } |
diff --git a/tests/Wallabag/CoreBundle/Command/CleanDuplicatesCommandTest.php b/tests/Wallabag/CoreBundle/Command/CleanDuplicatesCommandTest.php new file mode 100644 index 00000000..e6e57f30 --- /dev/null +++ b/tests/Wallabag/CoreBundle/Command/CleanDuplicatesCommandTest.php | |||
@@ -0,0 +1,108 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Tests\Wallabag\CoreBundle\Command; | ||
4 | |||
5 | use Symfony\Bundle\FrameworkBundle\Console\Application; | ||
6 | use Symfony\Component\Console\Tester\CommandTester; | ||
7 | use Wallabag\CoreBundle\Command\CleanDuplicatesCommand; | ||
8 | use Tests\Wallabag\CoreBundle\WallabagCoreTestCase; | ||
9 | use Wallabag\CoreBundle\Entity\Entry; | ||
10 | |||
11 | class CleanDuplicatesCommandTest extends WallabagCoreTestCase | ||
12 | { | ||
13 | public function testRunCleanDuplicates() | ||
14 | { | ||
15 | $application = new Application($this->getClient()->getKernel()); | ||
16 | $application->add(new CleanDuplicatesCommand()); | ||
17 | |||
18 | $command = $application->find('wallabag:clean-duplicates'); | ||
19 | |||
20 | $tester = new CommandTester($command); | ||
21 | $tester->execute([ | ||
22 | 'command' => $command->getName(), | ||
23 | ]); | ||
24 | |||
25 | $this->assertContains('Cleaning through 3 user accounts', $tester->getDisplay()); | ||
26 | $this->assertContains('Finished cleaning. 0 duplicates found in total', $tester->getDisplay()); | ||
27 | } | ||
28 | |||
29 | public function testRunCleanDuplicatesCommandWithBadUsername() | ||
30 | { | ||
31 | $application = new Application($this->getClient()->getKernel()); | ||
32 | $application->add(new CleanDuplicatesCommand()); | ||
33 | |||
34 | $command = $application->find('wallabag:clean-duplicates'); | ||
35 | |||
36 | $tester = new CommandTester($command); | ||
37 | $tester->execute([ | ||
38 | 'command' => $command->getName(), | ||
39 | 'username' => 'unknown', | ||
40 | ]); | ||
41 | |||
42 | $this->assertContains('User "unknown" not found', $tester->getDisplay()); | ||
43 | } | ||
44 | |||
45 | public function testRunCleanDuplicatesCommandForUser() | ||
46 | { | ||
47 | $application = new Application($this->getClient()->getKernel()); | ||
48 | $application->add(new CleanDuplicatesCommand()); | ||
49 | |||
50 | $command = $application->find('wallabag:clean-duplicates'); | ||
51 | |||
52 | $tester = new CommandTester($command); | ||
53 | $tester->execute([ | ||
54 | 'command' => $command->getName(), | ||
55 | 'username' => 'admin', | ||
56 | ]); | ||
57 | |||
58 | $this->assertContains('Cleaned 0 duplicates for user admin', $tester->getDisplay()); | ||
59 | } | ||
60 | |||
61 | public function testDuplicate() | ||
62 | { | ||
63 | $url = 'http://www.lemonde.fr/sport/visuel/2017/05/05/rondelle-prison-blanchissage-comprendre-le-hockey-sur-glace_5122587_3242.html'; | ||
64 | $client = $this->getClient(); | ||
65 | $em = $client->getContainer()->get('doctrine.orm.entity_manager'); | ||
66 | |||
67 | $this->logInAs('admin'); | ||
68 | |||
69 | $nbEntries = $em->getRepository('WallabagCoreBundle:Entry')->findAllByUrlAndUserId($url, $this->getLoggedInUserId()); | ||
70 | $this->assertCount(0, $nbEntries); | ||
71 | |||
72 | $user = $em->getRepository('WallabagUserBundle:User')->findOneById($this->getLoggedInUserId()); | ||
73 | |||
74 | $entry1 = new Entry($user); | ||
75 | $entry1->setUrl($url); | ||
76 | |||
77 | $entry2 = new Entry($user); | ||
78 | $entry2->setUrl($url); | ||
79 | |||
80 | $em->persist($entry1); | ||
81 | $em->persist($entry2); | ||
82 | |||
83 | $em->flush(); | ||
84 | |||
85 | $nbEntries = $em->getRepository('WallabagCoreBundle:Entry')->findAllByUrlAndUserId($url, $this->getLoggedInUserId()); | ||
86 | $this->assertCount(2, $nbEntries); | ||
87 | |||
88 | $application = new Application($this->getClient()->getKernel()); | ||
89 | $application->add(new CleanDuplicatesCommand()); | ||
90 | |||
91 | $command = $application->find('wallabag:clean-duplicates'); | ||
92 | |||
93 | $tester = new CommandTester($command); | ||
94 | $tester->execute([ | ||
95 | 'command' => $command->getName(), | ||
96 | 'username' => 'admin', | ||
97 | ]); | ||
98 | |||
99 | $this->assertContains('Cleaned 1 duplicates for user admin', $tester->getDisplay()); | ||
100 | |||
101 | $nbEntries = $em->getRepository('WallabagCoreBundle:Entry')->findAllByUrlAndUserId($url, $this->getLoggedInUserId()); | ||
102 | $this->assertCount(1, $nbEntries); | ||
103 | |||
104 | $query = $em->createQuery('DELETE FROM Wallabag\CoreBundle\Entity\Entry e WHERE e.url = :url'); | ||
105 | $query->setParameter('url', $url); | ||
106 | $query->execute(); | ||
107 | } | ||
108 | } | ||
diff --git a/tests/Wallabag/CoreBundle/Command/ExportCommandTest.php b/tests/Wallabag/CoreBundle/Command/ExportCommandTest.php index 6798c5d7..b21f3318 100644 --- a/tests/Wallabag/CoreBundle/Command/ExportCommandTest.php +++ b/tests/Wallabag/CoreBundle/Command/ExportCommandTest.php | |||
@@ -70,7 +70,7 @@ class ExportCommandTest extends WallabagCoreTestCase | |||
70 | $tester->execute([ | 70 | $tester->execute([ |
71 | 'command' => $command->getName(), | 71 | 'command' => $command->getName(), |
72 | 'username' => 'admin', | 72 | 'username' => 'admin', |
73 | 'filepath' => 'specialexport.json' | 73 | 'filepath' => 'specialexport.json', |
74 | ]); | 74 | ]); |
75 | 75 | ||
76 | $this->assertFileExists('specialexport.json'); | 76 | $this->assertFileExists('specialexport.json'); |
diff --git a/tests/Wallabag/CoreBundle/Helper/ContentProxyTest.php b/tests/Wallabag/CoreBundle/Helper/ContentProxyTest.php index 5956b502..8abb1bbb 100644 --- a/tests/Wallabag/CoreBundle/Helper/ContentProxyTest.php +++ b/tests/Wallabag/CoreBundle/Helper/ContentProxyTest.php | |||
@@ -111,7 +111,7 @@ class ContentProxyTest extends \PHPUnit_Framework_TestCase | |||
111 | 111 | ||
112 | $this->assertEquals('http://domain.io', $entry->getUrl()); | 112 | $this->assertEquals('http://domain.io', $entry->getUrl()); |
113 | $this->assertEquals('my title', $entry->getTitle()); | 113 | $this->assertEquals('my title', $entry->getTitle()); |
114 | $this->assertEquals($this->fetchingErrorMessage . '<p><i>But we found a short description: </i></p>desc', $entry->getContent()); | 114 | $this->assertEquals($this->fetchingErrorMessage.'<p><i>But we found a short description: </i></p>desc', $entry->getContent()); |
115 | $this->assertEmpty($entry->getPreviewPicture()); | 115 | $this->assertEmpty($entry->getPreviewPicture()); |
116 | $this->assertEmpty($entry->getLanguage()); | 116 | $this->assertEmpty($entry->getLanguage()); |
117 | $this->assertEmpty($entry->getHttpStatus()); | 117 | $this->assertEmpty($entry->getHttpStatus()); |