diff --git a/app/config/config.yml b/app/config/config.yml
index 1a522ddbe..c4c151200 100644
--- a/app/config/config.yml
+++ b/app/config/config.yml
@@ -293,6 +293,11 @@ old_sound_rabbit_mq:
exchange_options:
name: 'wallabag.import.pocket_html'
type: topic
+ import_pocket_csv:
+ connection: default
+ exchange_options:
+ name: 'wallabag.import.pocket_csv'
+ type: topic
consumers:
import_pocket:
connection: default
@@ -411,6 +416,15 @@ old_sound_rabbit_mq:
name: 'wallabag.import.pocket_html'
callback: wallabag_import.consumer.amqp.pocket_html
qos_options: {prefetch_count: "%rabbitmq_prefetch_count%"}
+ import_pocket_csv:
+ connection: default
+ exchange_options:
+ name: 'wallabag.import.pocket_csv'
+ type: topic
+ queue_options:
+ name: 'wallabag.import.pocket_csv'
+ callback: wallabag_import.consumer.amqp.pocket_csv
+ qos_options: {prefetch_count: "%rabbitmq_prefetch_count%"}
fos_js_routing:
routes_to_expose:
diff --git a/app/config/services.yml b/app/config/services.yml
index cc7c5329c..311e7e72d 100644
--- a/app/config/services.yml
+++ b/app/config/services.yml
@@ -131,6 +131,11 @@ services:
$rabbitMqProducer: '@old_sound_rabbit_mq.import_pocket_html_producer'
$redisProducer: '@wallabag_import.producer.redis.pocket_html'
+ Wallabag\ImportBundle\Controller\PocketCsvController:
+ arguments:
+ $rabbitMqProducer: '@old_sound_rabbit_mq.import_pocket_csv_producer'
+ $redisProducer: '@wallabag_import.producer.redis.pocket_csv'
+
Wallabag\ImportBundle\:
resource: '../../src/Wallabag/ImportBundle/*'
exclude: '../../src/Wallabag/ImportBundle/{Consumer,Controller,Redis}'
@@ -412,6 +417,10 @@ services:
tags:
- { name: wallabag_import.import, alias: pocket_html }
+ Wallabag\ImportBundle\Import\PocketCsvImport:
+ tags:
+ - { name: wallabag_import.import, alias: pocket_csv }
+
# to factorize the proximity and bypass translation for prev & next
pagerfanta.view.default_wallabag:
class: Pagerfanta\View\OptionableView
diff --git a/app/config/services_rabbit.yml b/app/config/services_rabbit.yml
index e6963733a..fa182d124 100644
--- a/app/config/services_rabbit.yml
+++ b/app/config/services_rabbit.yml
@@ -21,6 +21,7 @@ services:
$omnivoreConsumer: '@old_sound_rabbit_mq.import_omnivore_consumer'
$shaarliConsumer: '@old_sound_rabbit_mq.import_shaarli_consumer'
$pocketHtmlConsumer: '@old_sound_rabbit_mq.import_pocket_html_consumer'
+ $pocketCsvConsumer: '@old_sound_rabbit_mq.import_pocket_csv_consumer'
wallabag_import.consumer.amqp.pocket:
class: Wallabag\ImportBundle\Consumer\AMQPEntryConsumer
@@ -86,3 +87,8 @@ services:
class: Wallabag\ImportBundle\Consumer\AMQPEntryConsumer
arguments:
$import: '@Wallabag\ImportBundle\Import\PocketHtmlImport'
+
+ wallabag_import.consumer.amqp.pocket_csv:
+ class: Wallabag\ImportBundle\Consumer\AMQPEntryConsumer
+ arguments:
+ $import: '@Wallabag\ImportBundle\Import\PocketCsvImport'
diff --git a/app/config/services_redis.yml b/app/config/services_redis.yml
index baeb1ccef..5280078f1 100644
--- a/app/config/services_redis.yml
+++ b/app/config/services_redis.yml
@@ -212,3 +212,19 @@ services:
class: Wallabag\ImportBundle\Consumer\RedisEntryConsumer
arguments:
$import: '@Wallabag\ImportBundle\Import\PocketHtmlImport'
+
+ # pocket csv
+ wallabag_import.queue.redis.pocket_csv:
+ class: Simpleue\Queue\RedisQueue
+ arguments:
+ $queueName: "wallabag.import.pocket_csv"
+
+ wallabag_import.producer.redis.pocket_csv:
+ class: Wallabag\ImportBundle\Redis\Producer
+ arguments:
+ - "@wallabag_import.queue.redis.pocket_csv"
+
+ wallabag_import.consumer.redis.pocket_csv:
+ class: Wallabag\ImportBundle\Consumer\RedisEntryConsumer
+ arguments:
+ $import: '@Wallabag\ImportBundle\Import\PocketCsvImport'
diff --git a/app/config/wallabag.yml b/app/config/wallabag.yml
index 2c5e53a00..44cadc57d 100644
--- a/app/config/wallabag.yml
+++ b/app/config/wallabag.yml
@@ -167,5 +167,11 @@ wallabag_core:
rule: _all ~ "https?://www\.lemonde\.fr/tiny.*"
wallabag_import:
- allow_mimetypes: ['application/octet-stream', 'application/json', 'text/plain', 'text/csv', 'text/html']
+ allow_mimetypes:
+ - 'application/octet-stream'
+ - 'application/json'
+ - 'text/plain'
+ - 'text/csv'
+ - 'text/html'
+ - 'application/vnd.ms-excel'
resource_dir: "%kernel.project_dir%/web/uploads/import"
diff --git a/src/Wallabag/ImportBundle/Command/ImportCommand.php b/src/Wallabag/ImportBundle/Command/ImportCommand.php
index 76bd4f451..2d9f876db 100644
--- a/src/Wallabag/ImportBundle/Command/ImportCommand.php
+++ b/src/Wallabag/ImportBundle/Command/ImportCommand.php
@@ -18,6 +18,7 @@ use Wallabag\ImportBundle\Import\FirefoxImport;
use Wallabag\ImportBundle\Import\InstapaperImport;
use Wallabag\ImportBundle\Import\OmnivoreImport;
use Wallabag\ImportBundle\Import\PinboardImport;
+use Wallabag\ImportBundle\Import\PocketCsvImport;
use Wallabag\ImportBundle\Import\PocketHtmlImport;
use Wallabag\ImportBundle\Import\ReadabilityImport;
use Wallabag\ImportBundle\Import\ShaarliImport;
@@ -43,6 +44,7 @@ class ImportCommand extends Command
private ElcuratorImport $elcuratorImport;
private ShaarliImport $shaarliImport;
private PocketHtmlImport $pocketHtmlImport;
+ private PocketCsvImport $pocketCsvImport;
public function __construct(
EntityManagerInterface $entityManager,
@@ -59,7 +61,8 @@ class ImportCommand extends Command
ElcuratorImport $elcuratorImport,
OmnivoreImport $omnivoreImport,
ShaarliImport $shaarliImport,
- PocketHtmlImport $pocketHtmlImport
+ PocketHtmlImport $pocketHtmlImport,
+ PocketCsvImport $pocketCsvImport
) {
$this->entityManager = $entityManager;
$this->tokenStorage = $tokenStorage;
@@ -76,6 +79,7 @@ class ImportCommand extends Command
$this->elcuratorImport = $elcuratorImport;
$this->shaarliImport = $shaarliImport;
$this->pocketHtmlImport = $pocketHtmlImport;
+ $this->pocketCsvImport = $pocketCsvImport;
parent::__construct();
}
@@ -87,7 +91,7 @@ class ImportCommand extends Command
->setDescription('Import entries from a JSON export')
->addArgument('username', InputArgument::REQUIRED, 'User to populate')
->addArgument('filepath', InputArgument::REQUIRED, 'Path to the JSON file')
- ->addOption('importer', null, InputOption::VALUE_OPTIONAL, 'The importer to use: v1, v2, instapaper, pinboard, delicious, readability, firefox, chrome, elcurator, shaarli or pocket', 'v1')
+ ->addOption('importer', null, InputOption::VALUE_OPTIONAL, 'The importer to use: v1, v2, instapaper, pinboard, delicious, readability, firefox, chrome, elcurator, shaarli, pocket or pocket_csv', 'v1')
->addOption('markAsRead', null, InputOption::VALUE_OPTIONAL, 'Mark all entries as read', false)
->addOption('useUserId', null, InputOption::VALUE_NONE, 'Use user id instead of username to find account')
->addOption('disableContentUpdate', null, InputOption::VALUE_NONE, 'Disable fetching updated content from URL')
@@ -159,6 +163,9 @@ class ImportCommand extends Command
case 'pocket':
$import = $this->pocketHtmlImport;
break;
+ case 'pocket_csv':
+ $import = $this->pocketCsvImport;
+ break;
default:
$import = $this->wallabagV1Import;
}
diff --git a/src/Wallabag/ImportBundle/Consumer/RabbitMQConsumerTotalProxy.php b/src/Wallabag/ImportBundle/Consumer/RabbitMQConsumerTotalProxy.php
index 0d531d884..eaba83592 100644
--- a/src/Wallabag/ImportBundle/Consumer/RabbitMQConsumerTotalProxy.php
+++ b/src/Wallabag/ImportBundle/Consumer/RabbitMQConsumerTotalProxy.php
@@ -23,6 +23,7 @@ class RabbitMQConsumerTotalProxy
private Consumer $omnivoreConsumer;
private Consumer $shaarliConsumer;
private Consumer $pocketHtmlConsumer;
+ private Consumer $pocketCsvConsumer;
public function __construct(
Consumer $pocketConsumer,
@@ -37,7 +38,8 @@ class RabbitMQConsumerTotalProxy
Consumer $elcuratorConsumer,
Consumer $omnivoreConsumer,
Consumer $shaarliConsumer,
- Consumer $pocketHtmlConsumer
+ Consumer $pocketHtmlConsumer,
+ Consumer $pocketCsvConsumer
) {
$this->pocketConsumer = $pocketConsumer;
$this->readabilityConsumer = $readabilityConsumer;
@@ -52,6 +54,7 @@ class RabbitMQConsumerTotalProxy
$this->omnivoreConsumer = $omnivoreConsumer;
$this->shaarliConsumer = $shaarliConsumer;
$this->pocketHtmlConsumer = $pocketHtmlConsumer;
+ $this->pocketCsvConsumer = $pocketCsvConsumer;
}
/**
@@ -105,6 +108,9 @@ class RabbitMQConsumerTotalProxy
case 'pocket_html':
$consumer = $this->pocketHtmlConsumer;
break;
+ case 'pocket_csv':
+ $consumer = $this->pocketCsvConsumer;
+ break;
default:
return 0;
}
diff --git a/src/Wallabag/ImportBundle/Controller/ImportController.php b/src/Wallabag/ImportBundle/Controller/ImportController.php
index 6e6e5d7c1..a155e6eb5 100644
--- a/src/Wallabag/ImportBundle/Controller/ImportController.php
+++ b/src/Wallabag/ImportBundle/Controller/ImportController.php
@@ -60,6 +60,7 @@ class ImportController extends AbstractController
+ $this->rabbitMQConsumerTotalProxy->getTotalMessage('omnivore')
+ $this->rabbitMQConsumerTotalProxy->getTotalMessage('shaarli')
+ $this->rabbitMQConsumerTotalProxy->getTotalMessage('pocket_html')
+ + $this->rabbitMQConsumerTotalProxy->getTotalMessage('pocket_csv')
;
} catch (\Exception $e) {
$rabbitNotInstalled = true;
@@ -81,6 +82,7 @@ class ImportController extends AbstractController
+ $redis->llen('wallabag.import.omnivore')
+ $redis->llen('wallabag.import.shaarli')
+ $redis->llen('wallabag.import.pocket_html')
+ + $redis->llen('wallabag.import.pocket_csv')
;
} catch (\Exception $e) {
$redisNotInstalled = true;
diff --git a/src/Wallabag/ImportBundle/Controller/PocketCsvController.php b/src/Wallabag/ImportBundle/Controller/PocketCsvController.php
new file mode 100644
index 000000000..4eef3c0c8
--- /dev/null
+++ b/src/Wallabag/ImportBundle/Controller/PocketCsvController.php
@@ -0,0 +1,57 @@
+pocketCsvImport = $pocketCsvImport;
+ $this->craueConfig = $craueConfig;
+ $this->rabbitMqProducer = $rabbitMqProducer;
+ $this->redisProducer = $redisProducer;
+ }
+
+ /**
+ * @Route("/pocket_csv", name="import_pocket_csv")
+ */
+ public function indexAction(Request $request, TranslatorInterface $translator)
+ {
+ return parent::indexAction($request, $translator);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getImportService()
+ {
+ if ($this->craueConfig->get('import_with_rabbitmq')) {
+ $this->pocketCsvImport->setProducer($this->rabbitMqProducer);
+ } elseif ($this->craueConfig->get('import_with_redis')) {
+ $this->pocketCsvImport->setProducer($this->redisProducer);
+ }
+
+ return $this->pocketCsvImport;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getImportTemplate()
+ {
+ return '@WallabagImport/PocketCsv/index.html.twig';
+ }
+}
diff --git a/src/Wallabag/ImportBundle/Import/PocketCsvImport.php b/src/Wallabag/ImportBundle/Import/PocketCsvImport.php
new file mode 100644
index 000000000..62b830b0f
--- /dev/null
+++ b/src/Wallabag/ImportBundle/Import/PocketCsvImport.php
@@ -0,0 +1,156 @@
+filepath = $filepath;
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function validateEntry(array $importedEntry)
+ {
+ if (empty($importedEntry['url'])) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public function import()
+ {
+ if (!$this->user) {
+ $this->logger->error('Pocket CSV Import: user is not defined');
+
+ return false;
+ }
+
+ if (!file_exists($this->filepath) || !is_readable($this->filepath)) {
+ $this->logger->error('Pocket CSV Import: unable to read file', ['filepath' => $this->filepath]);
+
+ return false;
+ }
+
+ $entries = [];
+ $handle = fopen($this->filepath, 'r');
+ while (false !== ($data = fgetcsv($handle, 10240))) {
+ if ('title' === $data[0]) {
+ continue;
+ }
+
+ $entries[] = [
+ 'url' => $data[1],
+ 'title' => $data[0],
+ 'is_archived' => 'archive' === $data[4],
+ 'created_at' => $data[2],
+ 'tags' => $data[3],
+ ];
+ }
+ fclose($handle);
+
+ if (empty($entries)) {
+ $this->logger->error('PocketCsvImport: no entries in imported file');
+
+ return false;
+ }
+
+ if ($this->producer) {
+ $this->parseEntriesForProducer($entries);
+
+ return true;
+ }
+
+ $this->parseEntries($entries);
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function parseEntry(array $importedEntry)
+ {
+ $existingEntry = $this->em
+ ->getRepository(Entry::class)
+ ->findByUrlAndUserId($importedEntry['url'], $this->user->getId());
+
+ if (false !== $existingEntry) {
+ ++$this->skippedEntries;
+
+ return;
+ }
+
+ $entry = new Entry($this->user);
+ $entry->setUrl($importedEntry['url']);
+ $entry->setTitle($importedEntry['title']);
+
+ // update entry with content (in case fetching failed, the given entry will be return)
+ $this->fetchContent($entry, $importedEntry['url'], $importedEntry);
+
+ if (!empty($importedEntry['tags'])) {
+ $tags = str_replace('|', ',', $importedEntry['tags']);
+ $this->tagsAssigner->assignTagsToEntry(
+ $entry,
+ $tags,
+ $this->em->getUnitOfWork()->getScheduledEntityInsertions()
+ );
+ }
+
+ $entry->updateArchived($importedEntry['is_archived']);
+ $entry->setCreatedAt(\DateTime::createFromFormat('U', $importedEntry['created_at']));
+
+ $this->em->persist($entry);
+ ++$this->importedEntries;
+
+ return $entry;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setEntryAsRead(array $importedEntry)
+ {
+ $importedEntry['is_archived'] = 'archive';
+
+ return $importedEntry;
+ }
+}
diff --git a/src/Wallabag/ImportBundle/Resources/views/PocketCsv/index.html.twig b/src/Wallabag/ImportBundle/Resources/views/PocketCsv/index.html.twig
new file mode 100644
index 000000000..229171a8d
--- /dev/null
+++ b/src/Wallabag/ImportBundle/Resources/views/PocketCsv/index.html.twig
@@ -0,0 +1,45 @@
+{% extends "@WallabagCore/layout.html.twig" %}
+
+{% block title %}{{ 'import.pocket_csv.page_title'|trans }}{% endblock %}
+
+{% block content %}
+
+
+
+ {% include '@WallabagImport/Import/_information.html.twig' %}
+
+
+
{{ import.description|trans|raw }}
+
{{ 'import.pocket_csv.how_to'|trans }}
+
+
+ {{ form_start(form, {'method': 'POST'}) }}
+ {{ form_errors(form) }}
+
+
+
+
{{ 'import.form.mark_as_read_title'|trans }}
+ {{ form_widget(form.mark_as_read) }}
+ {{ form_label(form.mark_as_read) }}
+
+
+
+ {{ form_widget(form.save, {'attr': {'class': 'btn waves-effect waves-light'}}) }}
+
+ {{ form_rest(form) }}
+
+
+
+
+
+
+{% endblock %}
diff --git a/tests/Wallabag/ImportBundle/Controller/ImportControllerTest.php b/tests/Wallabag/ImportBundle/Controller/ImportControllerTest.php
index 76b802b6e..59dcbb7de 100644
--- a/tests/Wallabag/ImportBundle/Controller/ImportControllerTest.php
+++ b/tests/Wallabag/ImportBundle/Controller/ImportControllerTest.php
@@ -24,6 +24,6 @@ class ImportControllerTest extends WallabagCoreTestCase
$crawler = $client->request('GET', '/import/');
$this->assertSame(200, $client->getResponse()->getStatusCode());
- $this->assertSame(13, $crawler->filter('blockquote')->count());
+ $this->assertSame(14, $crawler->filter('blockquote')->count());
}
}
diff --git a/tests/Wallabag/ImportBundle/Controller/PocketCsvControllerTest.php b/tests/Wallabag/ImportBundle/Controller/PocketCsvControllerTest.php
new file mode 100644
index 000000000..d5a3d4df6
--- /dev/null
+++ b/tests/Wallabag/ImportBundle/Controller/PocketCsvControllerTest.php
@@ -0,0 +1,168 @@
+logInAs('admin');
+ $client = $this->getTestClient();
+
+ $crawler = $client->request('GET', '/import/pocket_csv');
+
+ $this->assertSame(200, $client->getResponse()->getStatusCode());
+ $this->assertSame(1, $crawler->filter('form[name=upload_import_file] > button[type=submit]')->count());
+ $this->assertSame(1, $crawler->filter('input[type=file]')->count());
+ }
+
+ public function testImportPocketCsvWithRabbitEnabled()
+ {
+ $this->logInAs('admin');
+ $client = $this->getTestClient();
+
+ $client->getContainer()->get(Config::class)->set('import_with_rabbitmq', 1);
+
+ $crawler = $client->request('GET', '/import/pocket_csv');
+
+ $this->assertSame(200, $client->getResponse()->getStatusCode());
+ $this->assertSame(1, $crawler->filter('form[name=upload_import_file] > button[type=submit]')->count());
+ $this->assertSame(1, $crawler->filter('input[type=file]')->count());
+
+ $client->getContainer()->get(Config::class)->set('import_with_rabbitmq', 0);
+ }
+
+ public function testImportPocketCsvBadFile()
+ {
+ $this->logInAs('admin');
+ $client = $this->getTestClient();
+
+ $crawler = $client->request('GET', '/import/pocket_csv');
+ $form = $crawler->filter('form[name=upload_import_file] > button[type=submit]')->form();
+
+ $data = [
+ 'upload_import_file[file]' => '',
+ ];
+
+ $client->submit($form, $data);
+
+ $this->assertSame(200, $client->getResponse()->getStatusCode());
+ }
+
+ public function testImportPocketCsvWithRedisEnabled()
+ {
+ $this->checkRedis();
+ $this->logInAs('admin');
+ $client = $this->getTestClient();
+ $client->getContainer()->get(Config::class)->set('import_with_redis', 1);
+
+ $crawler = $client->request('GET', '/import/pocket_csv');
+
+ $this->assertSame(200, $client->getResponse()->getStatusCode());
+ $this->assertSame(1, $crawler->filter('form[name=upload_import_file] > button[type=submit]')->count());
+ $this->assertSame(1, $crawler->filter('input[type=file]')->count());
+
+ $form = $crawler->filter('form[name=upload_import_file] > button[type=submit]')->form();
+
+ $file = new UploadedFile(__DIR__ . '/../fixtures/pocket.csv', 'Bookmarks');
+
+ $data = [
+ 'upload_import_file[file]' => $file,
+ ];
+
+ $client->submit($form, $data);
+
+ $this->assertSame(302, $client->getResponse()->getStatusCode());
+
+ $crawler = $client->followRedirect();
+
+ $this->assertGreaterThan(1, $body = $crawler->filter('body')->extract(['_text']));
+ $this->assertStringContainsString('flashes.import.notice.summary', $body[0]);
+
+ $this->assertNotEmpty($client->getContainer()->get(Client::class)->lpop('wallabag.import.pocket_csv'));
+
+ $client->getContainer()->get(Config::class)->set('import_with_redis', 0);
+ }
+
+ public function testImportWallabagWithPocketCsvFile()
+ {
+ $this->logInAs('admin');
+ $client = $this->getTestClient();
+
+ $crawler = $client->request('GET', '/import/pocket_csv');
+ $form = $crawler->filter('form[name=upload_import_file] > button[type=submit]')->form();
+
+ $file = new UploadedFile(__DIR__ . '/../fixtures/pocket.csv', 'Bookmarks');
+
+ $data = [
+ 'upload_import_file[file]' => $file,
+ ];
+
+ $client->submit($form, $data);
+
+ $this->assertSame(302, $client->getResponse()->getStatusCode());
+
+ $crawler = $client->followRedirect();
+
+ $this->assertGreaterThan(1, $body = $crawler->filter('body')->extract(['_text']));
+ $this->assertStringContainsString('flashes.import.notice.summary', $body[0]);
+
+ $entries = $client->getContainer()
+ ->get(EntityManagerInterface::class)
+ ->getRepository(Entry::class)
+ ->findBy(['user' => $this->getLoggedInUserId()]);
+
+ $expectedEntries = [
+ 'http://youmightnotneedjquery.com/,1600322788',
+ 'https://jp-lambert.me/est-ce-que-jai-besoin-d-un-scrum-master-604f5a471c73',
+ 'https://www.monde-diplomatique.fr/2020/09/HALIMI/62165',
+ 'https://www.reddit.com/r/DataHoarder/comments/ioupbk/archivebox_question_how_do_i_import_links_from_a/',
+ 'https://www.numerama.com/politique/646826-tu-vas-pleurer-les-premieres-fois-que-se-passe-t-il-au-sein-du-studio-dubisoft-derriere-trackmania.html#utm_medium=distibuted&utm_source=rss&utm_campaign=646826',
+ 'https://www.nouvelobs.com/rue89/20200911.OBS33165/comment-konbini-s-est-fait-pieger-par-un-pere-masculiniste.html',
+ 'https://reporterre.net/Des-abeilles-pour-resoudre-les-conflits-entre-les-humains-et-les-elephants',
+ ];
+
+ $matchedEntries = array_map(function ($expectedUrl) use ($entries) {
+ foreach ($entries as $entry) {
+ if ($entry->getUrl() === $expectedUrl) {
+ return $entry;
+ }
+ }
+
+ return null;
+ }, $expectedEntries);
+
+ $this->assertCount(\count($expectedEntries), $matchedEntries, 'Should have 7 entries imported from pocket.csv');
+ }
+
+ public function testImportWallabagWithEmptyFile()
+ {
+ $this->logInAs('admin');
+ $client = $this->getTestClient();
+
+ $crawler = $client->request('GET', '/import/pocket_csv');
+ $form = $crawler->filter('form[name=upload_import_file] > button[type=submit]')->form();
+
+ $file = new UploadedFile(__DIR__ . '/../fixtures/test.csv', 'test.csv');
+
+ $data = [
+ 'upload_import_file[file]' => $file,
+ ];
+
+ $client->submit($form, $data);
+
+ $this->assertSame(302, $client->getResponse()->getStatusCode());
+
+ $crawler = $client->followRedirect();
+
+ $this->assertGreaterThan(1, $body = $crawler->filter('body')->extract(['_text']));
+ $this->assertStringContainsString('flashes.import.notice.failed', $body[0]);
+ }
+}
diff --git a/tests/Wallabag/ImportBundle/Import/PocketCsvImportTest.php b/tests/Wallabag/ImportBundle/Import/PocketCsvImportTest.php
new file mode 100644
index 000000000..c3a7024b6
--- /dev/null
+++ b/tests/Wallabag/ImportBundle/Import/PocketCsvImportTest.php
@@ -0,0 +1,252 @@
+getPocketCsvImport();
+
+ $this->assertSame('Pocket CSV', $pocketCsvImport->getName());
+ $this->assertNotEmpty($pocketCsvImport->getUrl());
+ $this->assertSame('import.pocket_csv.description', $pocketCsvImport->getDescription());
+ }
+
+ public function testImport()
+ {
+ $pocketCsvImport = $this->getPocketCsvImport(false, 7);
+ $pocketCsvImport->setFilepath(__DIR__ . '/../fixtures/pocket.csv');
+
+ $entryRepo = $this->getMockBuilder(EntryRepository::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $entryRepo->expects($this->exactly(7))
+ ->method('findByUrlAndUserId')
+ ->willReturn(false);
+
+ $this->em
+ ->expects($this->any())
+ ->method('getRepository')
+ ->willReturn($entryRepo);
+
+ $entry = $this->getMockBuilder(Entry::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->contentProxy
+ ->expects($this->exactly(7))
+ ->method('updateEntry')
+ ->willReturn($entry);
+
+ $res = $pocketCsvImport->import();
+
+ $this->assertTrue($res);
+ $this->assertSame(['skipped' => 0, 'imported' => 7, 'queued' => 0], $pocketCsvImport->getSummary());
+ }
+
+ public function testImportAndMarkAllAsRead()
+ {
+ $pocketCsvImport = $this->getPocketCsvImport(false, 1);
+ $pocketCsvImport->setFilepath(__DIR__ . '/../fixtures/pocket.csv');
+
+ $entryRepo = $this->getMockBuilder(EntryRepository::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $entryRepo->expects($this->exactly(7))
+ ->method('findByUrlAndUserId')
+ ->will($this->onConsecutiveCalls(false, true));
+
+ $this->em
+ ->expects($this->any())
+ ->method('getRepository')
+ ->willReturn($entryRepo);
+
+ $this->contentProxy
+ ->expects($this->exactly(1))
+ ->method('updateEntry')
+ ->willReturn(new Entry($this->user));
+
+ // check that every entry persisted are archived
+ $this->em
+ ->expects($this->any())
+ ->method('persist')
+ ->with($this->callback(fn ($persistedEntry) => (bool) $persistedEntry->isArchived()));
+
+ $res = $pocketCsvImport
+ ->setMarkAsRead(true)
+ ->import();
+
+ $this->assertTrue($res);
+
+ $this->assertSame(['skipped' => 6, 'imported' => 1, 'queued' => 0], $pocketCsvImport->getSummary());
+ }
+
+ public function testImportWithRabbit()
+ {
+ $pocketCsvImport = $this->getPocketCsvImport();
+ $pocketCsvImport->setFilepath(__DIR__ . '/../fixtures/pocket.csv');
+
+ $entryRepo = $this->getMockBuilder(EntryRepository::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $entryRepo->expects($this->never())
+ ->method('findByUrlAndUserId');
+
+ $this->em
+ ->expects($this->never())
+ ->method('getRepository');
+
+ $entry = $this->getMockBuilder(Entry::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->contentProxy
+ ->expects($this->never())
+ ->method('updateEntry');
+
+ $producer = $this->getMockBuilder(\OldSound\RabbitMqBundle\RabbitMq\Producer::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $producer
+ ->expects($this->exactly(7))
+ ->method('publish');
+
+ $pocketCsvImport->setProducer($producer);
+
+ $res = $pocketCsvImport->setMarkAsRead(true)->import();
+
+ $this->assertTrue($res);
+ $this->assertSame(['skipped' => 0, 'imported' => 0, 'queued' => 7], $pocketCsvImport->getSummary());
+ }
+
+ public function testImportWithRedis()
+ {
+ $pocketCsvImport = $this->getPocketCsvImport();
+ $pocketCsvImport->setFilepath(__DIR__ . '/../fixtures/pocket.csv');
+
+ $entryRepo = $this->getMockBuilder(EntryRepository::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $entryRepo->expects($this->never())
+ ->method('findByUrlAndUserId');
+
+ $this->em
+ ->expects($this->never())
+ ->method('getRepository');
+
+ $entry = $this->getMockBuilder(Entry::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->contentProxy
+ ->expects($this->never())
+ ->method('updateEntry');
+
+ $factory = new RedisMockFactory();
+ $redisMock = $factory->getAdapter(Client::class, true);
+
+ $queue = new RedisQueue($redisMock, 'pocket_csv');
+ $producer = new Producer($queue);
+
+ $pocketCsvImport->setProducer($producer);
+
+ $res = $pocketCsvImport->setMarkAsRead(true)->import();
+
+ $this->assertTrue($res);
+ $this->assertSame(['skipped' => 0, 'imported' => 0, 'queued' => 7], $pocketCsvImport->getSummary());
+
+ $this->assertNotEmpty($redisMock->lpop('pocket_csv'));
+ }
+
+ public function testImportBadFile()
+ {
+ $pocketCsvImport = $this->getPocketCsvImport();
+ $pocketCsvImport->setFilepath(__DIR__ . '/../fixtures/Import/wallabag-v1.jsonx');
+
+ $res = $pocketCsvImport->import();
+
+ $this->assertFalse($res);
+
+ $records = $this->logHandler->getRecords();
+ $this->assertStringContainsString('Pocket CSV Import: unable to read file', $records[0]['message']);
+ $this->assertSame('ERROR', $records[0]['level_name']);
+ }
+
+ public function testImportUserNotDefined()
+ {
+ $pocketCsvImport = $this->getPocketCsvImport(true);
+ $pocketCsvImport->setFilepath(__DIR__ . '/../fixtures/pocket.csv');
+
+ $res = $pocketCsvImport->import();
+
+ $this->assertFalse($res);
+
+ $records = $this->logHandler->getRecords();
+ $this->assertStringContainsString('Pocket CSV Import: user is not defined', $records[0]['message']);
+ $this->assertSame('ERROR', $records[0]['level_name']);
+ }
+
+ private function getPocketCsvImport($unsetUser = false, $dispatched = 0)
+ {
+ $this->user = new User();
+
+ $this->em = $this->getMockBuilder(EntityManager::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->contentProxy = $this->getMockBuilder(ContentProxy::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->tagsAssigner = $this->getMockBuilder(TagsAssigner::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $dispatcher = $this->getMockBuilder(EventDispatcher::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $dispatcher
+ ->expects($this->exactly($dispatched))
+ ->method('dispatch');
+
+ $this->logHandler = new TestHandler();
+ $logger = new Logger('test', [$this->logHandler]);
+
+ $wallabag = new PocketCsvImport($this->em, $this->contentProxy, $this->tagsAssigner, $dispatcher, $logger);
+
+ if (false === $unsetUser) {
+ $wallabag->setUser($this->user);
+ }
+
+ return $wallabag;
+ }
+}
diff --git a/tests/Wallabag/ImportBundle/fixtures/pocket.csv b/tests/Wallabag/ImportBundle/fixtures/pocket.csv
new file mode 100644
index 000000000..085d3d86e
--- /dev/null
+++ b/tests/Wallabag/ImportBundle/fixtures/pocket.csv
@@ -0,0 +1,10 @@
+title,url,time_added,tags,status
+You Might Not Need jQuery,http://youmightnotneedjquery.com/,1600322788,,unread
+Est-ce que j’ai besoin d’un Scrum Master ? | by Jean-Pierre Lambert | Jean-,https://jp-lambert.me/est-ce-que-jai-besoin-d-un-scrum-master-604f5a471c73,1600172739,,unread
+"Avec les accusés d’El Halia, par Gisèle Halimi (Le Monde diplomatique, sept",https://www.monde-diplomatique.fr/2020/09/HALIMI/62165,1599806347,,unread
+ArchiveBox question: How do I import links from a RSS feed?,https://www.reddit.com/r/DataHoarder/comments/ioupbk/archivebox_question_how_do_i_import_links_from_a/,1600961496,,archive
+« Tu vas pleurer les premières fois » : que se passe-t-il au sein du studio,https://www.numerama.com/politique/646826-tu-vas-pleurer-les-premieres-fois-que-se-passe-t-il-au-sein-du-studio-dubisoft-derriere-trackmania.html#utm_medium=distibuted&utm_source=rss&utm_campaign=646826,1599809025,,unread
+Comment Konbini s’est fait piéger par un « père masculiniste »,https://www.nouvelobs.com/rue89/20200911.OBS33165/comment-konbini-s-est-fait-pieger-par-un-pere-masculiniste.html,1599819251,,archive
+"Des abeilles pour résoudre les « conflits » entre les humains
+
+et les élépha",https://reporterre.net/Des-abeilles-pour-resoudre-les-conflits-entre-les-humains-et-les-elephants,1599890673,,unread
\ No newline at end of file
diff --git a/tests/Wallabag/ImportBundle/fixtures/test.csv b/tests/Wallabag/ImportBundle/fixtures/test.csv
new file mode 100644
index 000000000..e69de29bb
diff --git a/translations/messages.en.yml b/translations/messages.en.yml
index e5a195bbb..ed09d6347 100644
--- a/translations/messages.en.yml
+++ b/translations/messages.en.yml
@@ -542,6 +542,10 @@ import:
page_title: Import > Pocket HTML
description: This importer will import all your Pocket bookmarks (via HTML export). Just go to https://getpocket.com/export, then export the HTML file. An HTML file will be downloaded (like "ril_export.html").
how_to: Please choose the bookmark backup file and click on the button below to import it. Note that the process may take a long time since all articles have to be fetched.
+ pocket_csv:
+ page_title: Import > Pocket CSV
+ description: This importer will import all your Pocket bookmarks (via CSV export). Just go to https://getpocket.com/export, then export the file. A ZIP file will be downloaded (like "pocket.zip"). Extract it, you will obtain a CSV file, called "part_000000.csv".
+ how_to: Please choose the bookmark backup file and click on the button below to import it. Note that the process may take a long time since all articles have to be fetched.
developer:
page_title: API clients management
welcome_message: Welcome to the wallabag API
diff --git a/translations/messages.fr.yml b/translations/messages.fr.yml
index c10a124ff..421e30996 100644
--- a/translations/messages.fr.yml
+++ b/translations/messages.fr.yml
@@ -530,6 +530,19 @@ import:
page_title: Importer > del.icio.us
how_to: Choisissez le fichier de votre export Delicious et cliquez sur le bouton ci-dessous pour l'importer.
description: Depuis 2021, vous pouvez à nouveau exporter vos données depuis Delicious (https://del.icio.us/export). Choisissez le format "JSON" et téléchargez le (un fichier du genre "delicious_export.2021.02.06_21.10.json").
+ shaarli:
+ page_title: Importer > Shaarli
+ description: Cet importateur importera toutes vos signets Shaarli. Il suffit d'aller à la section Outils, puis dans « Base de données d'exportation », choisissez vos signets et exportez-les. Vous obtiendrez un fichier HTML.
+ how_to: Veuillez sélectionner le fichier de sauvegarde de signet et cliquez sur le bouton ci-dessous pour l'importer. Notez que le processus peut prendre beaucoup de temps puisque tous les articles doivent être récupérés.
+ pocket_html:
+ page_title: Importer > Pocket HTML
+ description: Cet importateur importera toutes vos signets Pocket (via exportation HTML). Il suffit d'aller à https://getpocket.com/export, puis d'exporter le fichier HTML. Un fichier HTML sera téléchargé (comme « ril_export.html »).
+ how_to: Veuillez choisir le fichier de sauvegarde de signets et cliquez sur le bouton ci-dessous pour l'importer. Pensez au fait que le processus peut prendre longtemps puisque tous les articles doivent être récupérés.
+ pocket_csv:
+ page_title: Importer > Pocket CSV
+ description: Cet importateur importera toutes vos signets Pocket (via exportation CSV). Il suffit d'aller à https://getpocket.com/export, puis d'exporter le fichier. Un fichier ZIP sera téléchargé (comme « pocket.zip »). Décompressez le et vous obtiendrez un fichier CSV appelé "part_000000.csv".
+ how_to: Veuillez choisir le fichier de sauvegarde de signets et cliquez sur le bouton ci-dessous pour l'importer. Pensez au fait que le processus peut prendre longtemps puisque tous les articles doivent être récupérés.
+
developer:
page_title: Gestion des clients API
welcome_message: Bienvenue sur l’API de wallabag