aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/Wallabag
diff options
context:
space:
mode:
Diffstat (limited to 'src/Wallabag')
-rw-r--r--src/Wallabag/ImportBundle/Command/ImportCommand.php107
-rw-r--r--src/Wallabag/ImportBundle/Controller/PocketController.php2
-rw-r--r--src/Wallabag/ImportBundle/Controller/WallabagV1Controller.php58
-rw-r--r--src/Wallabag/ImportBundle/DependencyInjection/Configuration.php2
-rw-r--r--src/Wallabag/ImportBundle/DependencyInjection/WallabagImportExtension.php1
-rw-r--r--src/Wallabag/ImportBundle/Import/PocketImport.php7
-rw-r--r--src/Wallabag/ImportBundle/Import/WallabagV1Import.php137
-rw-r--r--src/Wallabag/ImportBundle/Resources/config/services.yml7
-rw-r--r--src/Wallabag/ImportBundle/Resources/views/Import/index.html.twig1
-rw-r--r--src/Wallabag/ImportBundle/Resources/views/WallabagV1/index.html.twig30
-rw-r--r--src/Wallabag/ImportBundle/Tests/Import/WallabagV1ImportTest.php96
-rw-r--r--src/Wallabag/ImportBundle/Tests/fixtures/wallabag-v1.json50
12 files changed, 404 insertions, 94 deletions
diff --git a/src/Wallabag/ImportBundle/Command/ImportCommand.php b/src/Wallabag/ImportBundle/Command/ImportCommand.php
index 3fb8927d..dfbfc2f7 100644
--- a/src/Wallabag/ImportBundle/Command/ImportCommand.php
+++ b/src/Wallabag/ImportBundle/Command/ImportCommand.php
@@ -7,118 +7,47 @@ use Symfony\Component\Config\Definition\Exception\Exception;
7use Symfony\Component\Console\Input\InputArgument; 7use Symfony\Component\Console\Input\InputArgument;
8use Symfony\Component\Console\Input\InputInterface; 8use Symfony\Component\Console\Input\InputInterface;
9use Symfony\Component\Console\Output\OutputInterface; 9use Symfony\Component\Console\Output\OutputInterface;
10use Symfony\Component\Console\Helper\ProgressBar;
11use Wallabag\CoreBundle\Entity\Entry;
12use Wallabag\CoreBundle\Tools\Utils;
13 10
14class ImportCommand extends ContainerAwareCommand 11class ImportCommand extends ContainerAwareCommand
15{ 12{
16 protected function configure() 13 protected function configure()
17 { 14 {
18 $this 15 $this
19 ->setName('wallabag:import') 16 ->setName('wallabag:import-v1')
20 ->setDescription('Import entries from JSON file') 17 ->setDescription('Import entries from a JSON export from a wallabag v1 instance')
21 ->addArgument( 18 ->addArgument('userId', InputArgument::REQUIRED, 'User ID to populate')
22 'userId', 19 ->addArgument('filepath', InputArgument::REQUIRED, 'Path to the JSON file')
23 InputArgument::REQUIRED, 20 ;
24 'user ID to populate'
25 );
26 } 21 }
27 22
28 protected function execute(InputInterface $input, OutputInterface $output) 23 protected function execute(InputInterface $input, OutputInterface $output)
29 { 24 {
30 $now = new \DateTime(); 25 $output->writeln('Start : '.(new \DateTime())->format('d-m-Y G:i:s').' ---');
31 $output->writeln('<comment>Start : '.$now->format('d-m-Y G:i:s').' ---</comment>');
32
33 // Importing CSV on DB via Doctrine ORM
34 $this->import($input, $output);
35
36 $now = new \DateTime();
37 $output->writeln('<comment>End : '.$now->format('d-m-Y G:i:s').' ---</comment>');
38 }
39
40 protected function import(InputInterface $input, OutputInterface $output)
41 {
42 // Getting php array of data from CSV
43 $data = $this->get($input, $output);
44 26
45 $em = $this->getContainer()->get('doctrine')->getManager(); 27 $em = $this->getContainer()->get('doctrine')->getManager();
46 // Turning off doctrine default logs queries for saving memory 28 // Turning off doctrine default logs queries for saving memory
47 $em->getConnection()->getConfiguration()->setSQLLogger(null); 29 $em->getConnection()->getConfiguration()->setSQLLogger(null);
48 30
49 // Define the size of record, the frequency for persisting the data and the current index of records 31 $user = $em->getRepository('WallabagUserBundle:User')->findOneById($input->getArgument('userId'));
50 $size = count($data);
51 $batchSize = 20;
52 $i = 1;
53
54 $user = $em->getRepository('WallabagUserBundle:User')
55 ->findOneById($input->getArgument('userId'));
56 32
57 if (!is_object($user)) { 33 if (!is_object($user)) {
58 throw new Exception('User not found'); 34 throw new Exception(sprintf('User with id "%s" not found', $input->getArgument('userId')));
59 } 35 }
60 36
61 $progress = new ProgressBar($output, $size); 37 $wallabag = $this->getContainer()->get('wallabag_import.wallabag_v1.import');
62 $progress->start(); 38 $res = $wallabag
39 ->setUser($user)
40 ->setFilepath($input->getArgument('filepath'))
41 ->import();
63 42
64 foreach ($data as $object) { 43 if (true === $res) {
65 $array = (array) $object; 44 $summary = $wallabag->getSummary();
66 $entry = $em->getRepository('WallabagCoreBundle:Entry') 45 $output->writeln('<info>'.$summary['imported'].' imported</info>');
67 ->findOneByUrl($array['url']); 46 $output->writeln('<comment>'.$summary['skipped'].' already saved</comment>');
68
69 if (!is_object($entry)) {
70 $entry = new Entry($user);
71 $entry->setUrl($array['url']);
72 }
73
74 $entry->setTitle($array['title']);
75 $entry->setArchived($array['is_read']);
76 $entry->setStarred($array['is_fav']);
77 $entry->setContent($array['content']);
78 $entry->setReadingTime(Utils::getReadingTime($array['content']));
79
80 $em->persist($entry);
81
82 if (($i % $batchSize) === 0) {
83 $em->flush();
84 $progress->advance($batchSize);
85
86 $now = new \DateTime();
87 $output->writeln(' of entries imported ... | '.$now->format('d-m-Y G:i:s'));
88 }
89 ++$i;
90 } 47 }
91 48
92 $em->flush();
93 $em->clear(); 49 $em->clear();
94 $progress->finish();
95 }
96
97 protected function convert($filename)
98 {
99 if (!file_exists($filename) || !is_readable($filename)) {
100 return false;
101 }
102
103 $header = null;
104 $data = array();
105
106 if (($handle = fopen($filename, 'r')) !== false) {
107 while (($row = fgets($handle)) !== false) {
108 $data = json_decode($row);
109 }
110 fclose($handle);
111 }
112
113 return $data;
114 }
115
116 protected function get(InputInterface $input, OutputInterface $output)
117 {
118 $filename = __DIR__.'/../../../../web/uploads/import/'.$input->getArgument('userId').'.json';
119
120 $data = $this->convert($filename);
121 50
122 return $data; 51 $output->writeln('End : '.(new \DateTime())->format('d-m-Y G:i:s').' ---');
123 } 52 }
124} 53}
diff --git a/src/Wallabag/ImportBundle/Controller/PocketController.php b/src/Wallabag/ImportBundle/Controller/PocketController.php
index 61eeba43..ebcee099 100644
--- a/src/Wallabag/ImportBundle/Controller/PocketController.php
+++ b/src/Wallabag/ImportBundle/Controller/PocketController.php
@@ -51,7 +51,7 @@ class PocketController extends Controller
51 51
52 if (true === $pocket->import()) { 52 if (true === $pocket->import()) {
53 $summary = $pocket->getSummary(); 53 $summary = $pocket->getSummary();
54 $message = $summary['imported'].' entrie(s) imported, '.$summary['skipped'].' already saved.'; 54 $message = 'Import summary: '.$summary['imported'].' imported, '.$summary['skipped'].' already saved.';
55 } 55 }
56 56
57 $this->get('session')->getFlashBag()->add( 57 $this->get('session')->getFlashBag()->add(
diff --git a/src/Wallabag/ImportBundle/Controller/WallabagV1Controller.php b/src/Wallabag/ImportBundle/Controller/WallabagV1Controller.php
new file mode 100644
index 00000000..de200184
--- /dev/null
+++ b/src/Wallabag/ImportBundle/Controller/WallabagV1Controller.php
@@ -0,0 +1,58 @@
1<?php
2
3namespace Wallabag\ImportBundle\Controller;
4
5use Symfony\Bundle\FrameworkBundle\Controller\Controller;
6use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
7use Symfony\Component\HttpFoundation\Request;
8use Wallabag\ImportBundle\Form\Type\UploadImportType;
9
10class WallabagV1Controller extends Controller
11{
12 /**
13 * @Route("/import/wallabag-v1", name="import_wallabag_v1")
14 */
15 public function indexAction(Request $request)
16 {
17 $importForm = $this->createForm(new UploadImportType());
18 $importForm->handleRequest($request);
19 $user = $this->getUser();
20
21 if ($importForm->isValid()) {
22 $file = $importForm->get('file')->getData();
23 $name = $user->getId().'.json';
24
25 if (in_array($file->getClientMimeType(), $this->getParameter('wallabag_import.allow_mimetypes')) && $file->move($this->getParameter('wallabag_import.resource_dir'), $name)) {
26 $wallabag = $this->get('wallabag_import.wallabag_v1.import');
27 $res = $wallabag
28 ->setUser($this->getUser())
29 ->setFilepath($this->getParameter('wallabag_import.resource_dir').'/'.$name)
30 ->import();
31
32 $message = 'Import failed, please try again.';
33 if (true === $res) {
34 $summary = $wallabag->getSummary();
35 $message = 'Import summary: '.$summary['imported'].' imported, '.$summary['skipped'].' already saved.';
36
37 @unlink($this->getParameter('wallabag_import.resource_dir').'/'.$name);
38 }
39
40 $this->get('session')->getFlashBag()->add(
41 'notice',
42 $message
43 );
44
45 return $this->redirect($this->generateUrl('homepage'));
46 } else {
47 $this->get('session')->getFlashBag()->add(
48 'notice',
49 'Error while processing import. Please verify your import file.'
50 );
51 }
52 }
53
54 return $this->render('WallabagImportBundle:WallabagV1:index.html.twig', [
55 'form' => $importForm->createView(),
56 ]);
57 }
58}
diff --git a/src/Wallabag/ImportBundle/DependencyInjection/Configuration.php b/src/Wallabag/ImportBundle/DependencyInjection/Configuration.php
index 2ef35463..39df9d3f 100644
--- a/src/Wallabag/ImportBundle/DependencyInjection/Configuration.php
+++ b/src/Wallabag/ImportBundle/DependencyInjection/Configuration.php
@@ -17,6 +17,8 @@ class Configuration implements ConfigurationInterface
17 ->arrayNode('allow_mimetypes') 17 ->arrayNode('allow_mimetypes')
18 ->prototype('scalar')->end() 18 ->prototype('scalar')->end()
19 ->end() 19 ->end()
20 ->scalarNode('resource_dir')
21 ->end()
20 ->end() 22 ->end()
21 ; 23 ;
22 24
diff --git a/src/Wallabag/ImportBundle/DependencyInjection/WallabagImportExtension.php b/src/Wallabag/ImportBundle/DependencyInjection/WallabagImportExtension.php
index 38163886..3f23c36b 100644
--- a/src/Wallabag/ImportBundle/DependencyInjection/WallabagImportExtension.php
+++ b/src/Wallabag/ImportBundle/DependencyInjection/WallabagImportExtension.php
@@ -14,6 +14,7 @@ class WallabagImportExtension extends Extension
14 $configuration = new Configuration(); 14 $configuration = new Configuration();
15 $config = $this->processConfiguration($configuration, $configs); 15 $config = $this->processConfiguration($configuration, $configs);
16 $container->setParameter('wallabag_import.allow_mimetypes', $config['allow_mimetypes']); 16 $container->setParameter('wallabag_import.allow_mimetypes', $config['allow_mimetypes']);
17 $container->setParameter('wallabag_import.resource_dir', $config['resource_dir']);
17 18
18 $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); 19 $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
19 $loader->load('services.yml'); 20 $loader->load('services.yml');
diff --git a/src/Wallabag/ImportBundle/Import/PocketImport.php b/src/Wallabag/ImportBundle/Import/PocketImport.php
index 1710d9d3..aeccc7bd 100644
--- a/src/Wallabag/ImportBundle/Import/PocketImport.php
+++ b/src/Wallabag/ImportBundle/Import/PocketImport.php
@@ -141,7 +141,7 @@ class PocketImport implements ImportInterface
141 141
142 $entries = $response->json(); 142 $entries = $response->json();
143 143
144 $this->parsePocketEntries($entries['list']); 144 $this->parseEntries($entries['list']);
145 145
146 return true; 146 return true;
147 } 147 }
@@ -194,11 +194,9 @@ class PocketImport implements ImportInterface
194 * 194 *
195 * @param $entries 195 * @param $entries
196 */ 196 */
197 private function parsePocketEntries($entries) 197 private function parseEntries($entries)
198 { 198 {
199 foreach ($entries as $pocketEntry) { 199 foreach ($entries as $pocketEntry) {
200 $entry = new Entry($this->user);
201
202 $url = isset($pocketEntry['resolved_url']) && $pocketEntry['resolved_url'] != '' ? $pocketEntry['resolved_url'] : $pocketEntry['given_url']; 200 $url = isset($pocketEntry['resolved_url']) && $pocketEntry['resolved_url'] != '' ? $pocketEntry['resolved_url'] : $pocketEntry['given_url'];
203 201
204 $existingEntry = $this->em 202 $existingEntry = $this->em
@@ -210,6 +208,7 @@ class PocketImport implements ImportInterface
210 continue; 208 continue;
211 } 209 }
212 210
211 $entry = new Entry($this->user);
213 $entry = $this->contentProxy->updateEntry($entry, $url); 212 $entry = $this->contentProxy->updateEntry($entry, $url);
214 213
215 // 0, 1, 2 - 1 if the item is archived - 2 if the item should be deleted 214 // 0, 1, 2 - 1 if the item is archived - 2 if the item should be deleted
diff --git a/src/Wallabag/ImportBundle/Import/WallabagV1Import.php b/src/Wallabag/ImportBundle/Import/WallabagV1Import.php
new file mode 100644
index 00000000..7b012674
--- /dev/null
+++ b/src/Wallabag/ImportBundle/Import/WallabagV1Import.php
@@ -0,0 +1,137 @@
1<?php
2
3namespace Wallabag\ImportBundle\Import;
4
5use Psr\Log\LoggerInterface;
6use Psr\Log\NullLogger;
7use Doctrine\ORM\EntityManager;
8use Wallabag\CoreBundle\Entity\Entry;
9use Wallabag\UserBundle\Entity\User;
10use Wallabag\CoreBundle\Tools\Utils;
11
12class WallabagV1Import implements ImportInterface
13{
14 private $user;
15 private $em;
16 private $logger;
17 private $skippedEntries = 0;
18 private $importedEntries = 0;
19 private $filepath;
20
21 public function __construct(EntityManager $em)
22 {
23 $this->em = $em;
24 $this->logger = new NullLogger();
25 }
26
27 public function setLogger(LoggerInterface $logger)
28 {
29 $this->logger = $logger;
30 }
31
32 /**
33 * We define the user in a custom call because on the import command there is no logged in user.
34 * So we can't retrieve user from the `security.token_storage` service.
35 *
36 * @param User $user
37 */
38 public function setUser(User $user)
39 {
40 $this->user = $user;
41
42 return $this;
43 }
44
45 /**
46 * {@inheritdoc}
47 */
48 public function getName()
49 {
50 return 'Wallabag v1';
51 }
52
53 /**
54 * {@inheritdoc}
55 */
56 public function getDescription()
57 {
58 return 'This importer will import all your wallabag v1 articles.';
59 }
60
61 /**
62 * {@inheritdoc}
63 */
64 public function import()
65 {
66 if (!$this->user) {
67 $this->logger->error('WallabagV1Import: user is not defined');
68
69 return false;
70 }
71
72 if (!file_exists($this->filepath) || !is_readable($this->filepath)) {
73 $this->logger->error('WallabagV1Import: unable to read file', array('filepath' => $this->filepath));
74
75 return false;
76 }
77
78 $this->parseEntries(json_decode(file_get_contents($this->filepath), true));
79
80 return true;
81 }
82
83 /**
84 * {@inheritdoc}
85 */
86 public function getSummary()
87 {
88 return [
89 'skipped' => $this->skippedEntries,
90 'imported' => $this->importedEntries,
91 ];
92 }
93
94 /**
95 * Set file path to the json file.
96 *
97 * @param string $filepath
98 */
99 public function setFilepath($filepath)
100 {
101 $this->filepath = $filepath;
102
103 return $this;
104 }
105
106 /**
107 * @param $entries
108 */
109 private function parseEntries($entries)
110 {
111 foreach ($entries as $importedEntry) {
112 $existingEntry = $this->em
113 ->getRepository('WallabagCoreBundle:Entry')
114 ->existByUrlAndUserId($importedEntry['url'], $this->user->getId());
115
116 if (false !== $existingEntry) {
117 ++$this->skippedEntries;
118 continue;
119 }
120
121 // @see ContentProxy->updateEntry
122 $entry = new Entry($this->user);
123 $entry->setUrl($importedEntry['url']);
124 $entry->setTitle($importedEntry['title']);
125 $entry->setArchived($importedEntry['is_read']);
126 $entry->setStarred($importedEntry['is_fav']);
127 $entry->setContent($importedEntry['content']);
128 $entry->setReadingTime(Utils::getReadingTime($importedEntry['content']));
129 $entry->setDomainName(parse_url($importedEntry['url'], PHP_URL_HOST));
130
131 $this->em->persist($entry);
132 ++$this->importedEntries;
133 }
134
135 $this->em->flush();
136 }
137}
diff --git a/src/Wallabag/ImportBundle/Resources/config/services.yml b/src/Wallabag/ImportBundle/Resources/config/services.yml
index f421821c..e73ec0c8 100644
--- a/src/Wallabag/ImportBundle/Resources/config/services.yml
+++ b/src/Wallabag/ImportBundle/Resources/config/services.yml
@@ -18,3 +18,10 @@ services:
18 calls: 18 calls:
19 - [ setClient, [ "@wallabag_import.pocket.client" ] ] 19 - [ setClient, [ "@wallabag_import.pocket.client" ] ]
20 - [ setLogger, [ "@logger" ]] 20 - [ setLogger, [ "@logger" ]]
21
22 wallabag_import.wallabag_v1.import:
23 class: Wallabag\ImportBundle\Import\WallabagV1Import
24 arguments:
25 - "@doctrine.orm.entity_manager"
26 calls:
27 - [ setLogger, [ "@logger" ]]
diff --git a/src/Wallabag/ImportBundle/Resources/views/Import/index.html.twig b/src/Wallabag/ImportBundle/Resources/views/Import/index.html.twig
index b068283a..bd51f730 100644
--- a/src/Wallabag/ImportBundle/Resources/views/Import/index.html.twig
+++ b/src/Wallabag/ImportBundle/Resources/views/Import/index.html.twig
@@ -9,6 +9,7 @@
9 {% trans %}Welcome on wallabag importer. Please select your previous service that you want to migrate.{% endtrans %} 9 {% trans %}Welcome on wallabag importer. Please select your previous service that you want to migrate.{% endtrans %}
10 <ul> 10 <ul>
11 <li><a href="{{ path('import_pocket') }}">Pocket</a></li> 11 <li><a href="{{ path('import_pocket') }}">Pocket</a></li>
12 <li><a href="{{ path('import_wallabag_v1') }}">Wallabag v1</a></li>
12 </ul> 13 </ul>
13 </div> 14 </div>
14 </div> 15 </div>
diff --git a/src/Wallabag/ImportBundle/Resources/views/WallabagV1/index.html.twig b/src/Wallabag/ImportBundle/Resources/views/WallabagV1/index.html.twig
new file mode 100644
index 00000000..328ab473
--- /dev/null
+++ b/src/Wallabag/ImportBundle/Resources/views/WallabagV1/index.html.twig
@@ -0,0 +1,30 @@
1{% extends "WallabagCoreBundle::layout.html.twig" %}
2{% block title %}{% trans %}import{% endtrans %}{% endblock %}
3
4{% block content %}
5
6<div class="row">
7 <div class="col s12">
8 <div class="card-panel settings">
9 <div class="row">
10 <div class="col s12">
11 {{ form_start(form, {'method': 'POST'}) }}
12 {{ form_errors(form) }}
13 <div class="row">
14 <div class="input-field col s12">
15 <p>{% trans %}Please select your wallabag export and click on the below button to upload and import it.{% endtrans %}</p>
16 {{ form_errors(form.file) }}
17 {{ form_widget(form.file) }}
18 </div>
19 </div>
20 <div class="hidden">{{ form_rest(form) }}</div>
21 <button class="btn waves-effect waves-light" type="submit" name="action">
22 {% trans %}Upload file{% endtrans %}
23 </button>
24 </form>
25 </div>
26 </div>
27 </div>
28 </div>
29</div>
30{% endblock %}
diff --git a/src/Wallabag/ImportBundle/Tests/Import/WallabagV1ImportTest.php b/src/Wallabag/ImportBundle/Tests/Import/WallabagV1ImportTest.php
new file mode 100644
index 00000000..fc66d402
--- /dev/null
+++ b/src/Wallabag/ImportBundle/Tests/Import/WallabagV1ImportTest.php
@@ -0,0 +1,96 @@
1<?php
2
3namespace Wallabag\ImportBundle\Tests\Import;
4
5use Wallabag\UserBundle\Entity\User;
6use Wallabag\ImportBundle\Import\WallabagV1Import;
7use Monolog\Logger;
8use Monolog\Handler\TestHandler;
9
10class WallabagV1ImportTest extends \PHPUnit_Framework_TestCase
11{
12 protected $user;
13 protected $em;
14 protected $logHandler;
15
16 private function getWallabagV1Import($unsetUser = false)
17 {
18 $this->user = new User();
19
20 $this->em = $this->getMockBuilder('Doctrine\ORM\EntityManager')
21 ->disableOriginalConstructor()
22 ->getMock();
23
24 $pocket = new WallabagV1Import($this->em);
25
26 $this->logHandler = new TestHandler();
27 $logger = new Logger('test', array($this->logHandler));
28 $pocket->setLogger($logger);
29
30 if (false === $unsetUser) {
31 $pocket->setUser($this->user);
32 }
33
34 return $pocket;
35 }
36
37 public function testInit()
38 {
39 $wallabagV1Import = $this->getWallabagV1Import();
40
41 $this->assertEquals('Wallabag v1', $wallabagV1Import->getName());
42 $this->assertEquals('This importer will import all your wallabag v1 articles.', $wallabagV1Import->getDescription());
43 }
44
45 public function testImport()
46 {
47 $wallabagV1Import = $this->getWallabagV1Import();
48 $wallabagV1Import->setFilepath(__DIR__.'/../fixtures/wallabag-v1.json');
49
50 $entryRepo = $this->getMockBuilder('Wallabag\CoreBundle\Repository\EntryRepository')
51 ->disableOriginalConstructor()
52 ->getMock();
53
54 $entryRepo->expects($this->exactly(3))
55 ->method('existByUrlAndUserId')
56 ->will($this->onConsecutiveCalls(false, true, false));
57
58 $this->em
59 ->expects($this->any())
60 ->method('getRepository')
61 ->willReturn($entryRepo);
62
63 $res = $wallabagV1Import->import();
64
65 $this->assertTrue($res);
66 $this->assertEquals(['skipped' => 1, 'imported' => 2], $wallabagV1Import->getSummary());
67 }
68
69 public function testImportBadFile()
70 {
71 $wallabagV1Import = $this->getWallabagV1Import();
72 $wallabagV1Import->setFilepath(__DIR__.'/../fixtures/wallabag-v1.jsonx');
73
74 $res = $wallabagV1Import->import();
75
76 $this->assertFalse($res);
77
78 $records = $this->logHandler->getRecords();
79 $this->assertContains('WallabagV1Import: unable to read file', $records[0]['message']);
80 $this->assertEquals('ERROR', $records[0]['level_name']);
81 }
82
83 public function testImportUserNotDefined()
84 {
85 $wallabagV1Import = $this->getWallabagV1Import(true);
86 $wallabagV1Import->setFilepath(__DIR__.'/../fixtures/wallabag-v1.json');
87
88 $res = $wallabagV1Import->import();
89
90 $this->assertFalse($res);
91
92 $records = $this->logHandler->getRecords();
93 $this->assertContains('WallabagV1Import: user is not defined', $records[0]['message']);
94 $this->assertEquals('ERROR', $records[0]['level_name']);
95 }
96}
diff --git a/src/Wallabag/ImportBundle/Tests/fixtures/wallabag-v1.json b/src/Wallabag/ImportBundle/Tests/fixtures/wallabag-v1.json
new file mode 100644
index 00000000..534343f8
--- /dev/null
+++ b/src/Wallabag/ImportBundle/Tests/fixtures/wallabag-v1.json
@@ -0,0 +1,50 @@
1[
2 {
3 "0": "1",
4 "1": "Framabag, un nouveau service libre et gratuit",
5 "2": "http://www.framablog.org/index.php/post/2014/02/05/Framabag-service-libre-gratuit-interview-developpeur",
6 "3": "0",
7 "4": "0",
8 "5": "\n<h2>Une interview de Nicolas, son développeur.</h2>\n<p><em>Il ne vous a sûrement pas échappé que notre consommation de contenus du Web est terriblement chronophage et particulièrement frustrante tout à la fois : non seulement nous passons beaucoup (trop ?) de temps en ligne à explorer les mines aurifères de la toile, y détectant pépites et filons, mais nous sommes surtout constamment en manque. Même si nous ne sommes pas dans le zapping frénétique si facilement dénoncé par les doctes psychologues qui pontifient sur les dangers du numérique pour les jeunes cervelles, il nous vient souvent le goût amer de l’inachevé : pas le temps de tout lire (<a href=\"http://fr.wiktionary.org/wiki/TLDR\">TL;DR</a> est devenu le clin d’œil mi-figue mi-raisin d’une génération de lecteurs pressés), pas trop le temps de réfléchir non plus hélas, pas le temps de suivre la ribambelle de liens associés à un article…<br /></em></p>\n<p><em>Pour nous donner bonne conscience, nous rangeons scrupuleusement un marque-page de plus dans un sous-dossier qui en comporte déjà 256, nous notons un élément de plus dans la toujours ridiculement longue toudouliste, bref nous remettons à plus tard, c’est-à-dire le plus souvent aux introuvables calendes grecques, le soin de lire vraiment un article jugé intéressant, de regarder une vidéo signalée par les rézossocios, de lire un chapitre entier d’un ouvrage disponible en ligne…<br /></em></p>\n<p><em>Alors bien sûr, à défaut de nous donner tout le temps qui serait nécessaire, des solutions existent pour nous permettre de « lire plus tard » en sauvegardant le précieux pollen de nos butinages de site en site, et d’en faire ultérieurement votre miel ; c’est bel et bon mais les ruches sont un peu distantes, ça s’appelle le cloud (nos amis techies m’ont bien expliqué mais j’ai seulement compris que des trucs à moi sont sur des machines lointaines, ça ne me rassure pas trop) et elles sont souvent propriétaires, ne laissant entrer que les <s>utilisateurs</s> consommateurs payants et qui consentent à leurs conditions. Sans compter que de gros bourdons viennent profiter plus ou moins discrètement de toutes ces traces de nous-mêmes qui permettent de monétiser notre profil : si je collecte sur ces services (ne les nommons pas, justement) une série d’articles sur l’idée de Nature chez Diderot, je recevrai diverses sollicitations pour devenir client de la boutique Nature &amp; Découverte du boulevard Diderot. Et si d’aventure les programmes de la NSA moulinent sur le service, je serai peut-être un jour dans une liste des militants naturistes indésirables sur les vols de la PanAm (je ne sais plus trop si je plaisante là, finalement…)<br /></em></p>\n<p><em>La bonne idée : « se constituer un réservoir de documents sélectionnés à parcourir plus tard » appelait donc une autre bonne idée, celle d’avoir le contrôle de ce réservoir, de notre collection personnelle. C’est Nicolas Lœuillet, ci-dessous interviewé, qui s’y est collé avec une belle application appelée euh… oui, appelée Wallabag.<br /></em></p>\n<p><em>Framasoft soutient d’autant plus son initiative qu’<a href=\"http://www.framablog.org/index.php/post/2014/01/31/Geektionnerd-Wallabag\">on lui a cherché des misères pour une histoire de nom</a> et qu’il est possible d’installer soi-même une copie de Wallabag sur son propre site.<br /></em></p>\n<p><em>Le petit plus de Framasoft, réseau toujours désireux de vous proposer des alternatives libératrices, c’est de vous proposer (sur inscription préalable) un accès au Framabag, autrement dit votre Wallabag sur un serveur Frama* avec notre garantie de confidentialité. Comme pour le Framanews, nous vous accueillons volontiers dans la limite de nos capacités, en vous invitant à vous lancer dans votre auto-hébergement de Wallabag.<br />Cet article est trop long ? Mettez-le dans <a href=\"http://www.framabag.org/index.php\">votre Framabag</a> et hop.<br /></em></p>\n<p><em>Framablog : Salut Nicolas… Tu peux te présenter brièvement ?<br /></em></p>\n<p>Salut ! Développeur PHP depuis quelques années maintenant (10 ans), j’ai voulu me remettre à niveau techniquement parlant (depuis 3 ans, j’ai pas mal lâché le clavier). Pour mes besoins persos, j’ai donc créé un petit projet pour remplacer une solution propriétaire existante. Sans aucune prétention, j’ai hébergé ce projet sur Github et comme c’est la seule solution <em>open source</em> de ce type, le nombre de personnes intéressées a augmenté …</p>\n<p><em>Les utilisateurs de services Framasoft ne le savent pas forcément, mais tu as déjà pas mal participé à la FramaGalaxie, non ?<br /></em></p>\n<p>En effet. J’ai commencé un plugin pour Framanews, <a href=\"https://github.com/nicosomb/ttrss-purge-accounts\">ttrss-purge-accounts</a>, qui permet de nettoyer la base de données de comptes plus utilisés. Mais ce <em>plugin</em> a besoin d’être terminé à 100% pour être intégré au sein de Framanews (et donc de Tiny Tiny RSS), si quelqu’un souhaite m’aider, il n’y a aucun souci.<br />J’ai aussi fait 1 ou 2 apparitions dans des traductions pour Framablog. Rien d’extraordinaire, je ne suis pas bilingue, ça me permet de m’entraîner.</p>\n<p><em>Parlons de suite de ce qui fâche : ton application Wallabag, elle s’appellait pas “Poche”, avant ? Tu nous racontes l’histoire ?<br /></em></p>\n<p>Euh en effet … Déjà, pourquoi <em>poche</em> ? Parce que l’un des trois « ténors » sur le marché s’appelle <em>Pocket</em>. Comme mon appli n’était destinée qu’à mon usage personnel au départ, je ne me suis pas torturé bien longtemps.</p>\n<p>Cet été, on a failli changer de nom, quand il y a eu de plus en plus d’utilisateurs. Et puis on s’est dit que poche, c’était pas mal, ça sonnait bien français et puis avec les quelques dizaines d’utilisateurs, on ne gênerait personne.</p>\n<p>C’est sans compter avec les sociétés américaines et leur fâcheuse manie de vouloir envoyer leurs avocats à tout bout de champ. Le 23 janvier, j’ai reçu un email de la part du cabinet d’avocats de Pocket me demandant de changer le nom, le logo, de ne plus utiliser le terme “read-it-later” (« lisez le plus tard ») et de ne plus dire que Pocket n’est pas gratuit (tout est parti d’<a href=\"https://twitter.com/wallabagapp/status/423786365944225792\">un tweet</a> où je qualifie Pocket de « non free » à savoir non libre). Bref, même si je semblais dans mon droit, j’ai quand même pris la décision de changer de nom et Wallabag est né, suite aux <a href=\"http://framadate.org/studs.php?sondage=llcp6ojpyc9pklha\">dizaines de propositions de nom reçues</a>. C’est un mélange entre le wallaby (de la famille des kangourous, qui stockent dans leur poche ce qui leur est cher) et <em>bag</em> (les termes sac / sacoche / besace sont énormément revenus). Mais maintenant, on va de l’avant, plus de temps à perdre avec ça, on a du pain sur la planche.<br /><img src=\"http://www.framablog.org/public/_img/framablog/wallaby_baby.jpg\" alt=\"wallaby avec bébé dans sa poche\" class=\"c1\" title=\"wallaby avec bébé dans sa poche\" /> crédit photo <a href=\"http://www.flickr.com/photos/26782864@N00/5027202234/in/photolist-8EeJ5A-h1TL6v-NEL81-cnNkSo-9YM1tv-7Kcg6b-8zpAoa-a1ZLMN-9YM39r-7h5SAD-8EeHfL-8EeFwu-dtVwnM-8uoME1-JEzXe-Gq4qy-92VJPR-Cxe1v-8H3D2J-a1ZFNs-9Y72K6-8EeGxL-5L53Fx-5NkENs-5U8CTY-5Nkssh-nkavF-9CrgwP-7sdCAa-duf2Kh-hZepzy-hZdU1e-hZeofF-hZekDg-hZegAY-hZeMZn-jaHgAf-8P87D2-5NgqRv-aT48QB-hZdV4Y-hZeC64-ERgps-5VYGGd-5VYJB1-5NkrFk-6Jxh7h-7h9PuQ-ERfMx-h1U1ih-h1USBx\">William Warby</a> qui autorise explicitement toute réutilisation.</p>\n<p><em>Bon, alors explique-moi ce que je vais pouvoir faire avec Framabag…<br /></em></p>\n<p>Alors Framabag, ça te permet de te créer un compte gratuitement et librement pour pouvoir utiliser Wallabag. Seule ton adresse email est nécessaire, on se charge d’installer et de mettre à jour Wallabag pour toi. Tu peux d’ailleurs profiter <a href=\"http://www.framasoft.net/#topPgCloud\">d’autres services proposés par Framasoft ici</a>.</p>\n<p>À ce jour, il y a 834 comptes créés sur Framabag.</p>\n<p><em>Vous avez vraiment conçu ce service afin qu’on puisse l’utiliser avec un maximum d’outils, non ?<br /></em></p>\n<p>Autour de l’application web, il existe déjà des applications pour smartphones (Android et Windows Phone), des extensions Firefox et Google Chrome.</p>\n<p>Comme Wallabag possède des flux RSS, c’est facile de lire les articles sauvegardés sur sa liseuse (si celle-ci permet de lire des flux RSS). Calibre (« logiciel de lecture, de gestion de bibliothèques et de conversion de fichiers numériques de type ebook ou livre électronique »,nous dit ubuntu-fr.org) intègre depuis quelques semaines maintenant la possibilité de récupérer les articles non lus, pratique pour faire un fichier ePub !</p>\n<p>D’autres applications web permettent l’intégration avec Wallabag (FreshRSS, Leed et Tiny Tiny RSS pour les agrégateurs de flux). L’API qui sera disponible dans la prochaine version de Wallabag permettra encore plus d’interactivité.</p>\n<p><em>Y a-t-il un mode de lecture hors ligne ou est-ce que c’est prévu pour les prochaines versions ?<br /></em></p>\n<p>Il y a un pseudo mode hors ligne, disponible avec l’application Android. On peut récupérer (via un flux RSS) les articles non lus que l’on a sauvegardés. Une fois déconnecté, on peut continuer à lire sur son smartphone ou sa tablette les articles. Par contre, il manque des fonctionnalités : quand tu marques un article comme lu, ce n’est pas synchronisé avec la version web de Wallabag. J’espère que je suis presque clair dans mes explications.</p>\n<p>Pour la v2, qui est déjà en cours de développement, où je suis bien aidé par Vincent Jousse, on aura la possibilité d’avoir un vrai mode hors ligne.</p>\n<p><em>Alors si on veut aider / participer / trifouiller le code / vous envoyer des retours, on fait comment ?<br /></em></p>\n<p>On peut aider de plusieurs façons :</p>\n<ul><li>utiliser wallabag et nous remonter les problèmes rencontrés ;</li>\n<li>participer au développement de l’application https://github.com/wallabag/wallabag Si Silex / Symfony2 / HTML5 / etc. te parlent, n’hésite pas !</li>\n<li>comme tout projet, le gros point noir est le manque de documentation. <a href=\"http://doc.wallabag.org\">Elle est dispo ici</a> mais il manque plein de choses et tout n’est pas à jour ;</li>\n<li>parler de Wallabag autour de vous ;</li>\n<li>il existe <a href=\"https://flattr.com/thing/1265480/poche-a-read-it-later-open-source-system\">un compte Flattr</a>.</li>\n</ul><p><em>Le mot de la fin…?<br /></em></p>\n<p>Merci à Framasoft d’accueillir et de soutenir Wallabag !</p>\n<p>La route est encore bien longue pour ne plus utiliser de solutions propriétaires, mais on devrait y arriver, non ?</p>\n<p><img src=\"http://www.framablog.org/public/_img/framablog/pleinLesPoches.png\" alt=\"framasoft plein les poches\" class=\"c1\" title=\"framasoft plein les poches\" /><br /><a href=\"http://framalab.org/gknd-creator/\">hackez Gégé !</a></p>\n",
9 "6": "1",
10 "id": "1",
11 "title": "Framabag, un nouveau service libre et gratuit",
12 "url": "http://www.framablog.org/index.php/post/2014/02/05/Framabag-service-libre-gratuit-interview-developpeur",
13 "is_read": "0",
14 "is_fav": "0",
15 "content": "\n<h2>Une interview de Nicolas, son développeur.</h2>\n<p><em>Il ne vous a sûrement pas échappé que notre consommation de contenus du Web est terriblement chronophage et particulièrement frustrante tout à la fois : non seulement nous passons beaucoup (trop ?) de temps en ligne à explorer les mines aurifères de la toile, y détectant pépites et filons, mais nous sommes surtout constamment en manque. Même si nous ne sommes pas dans le zapping frénétique si facilement dénoncé par les doctes psychologues qui pontifient sur les dangers du numérique pour les jeunes cervelles, il nous vient souvent le goût amer de l’inachevé : pas le temps de tout lire (<a href=\"http://fr.wiktionary.org/wiki/TLDR\">TL;DR</a> est devenu le clin d’œil mi-figue mi-raisin d’une génération de lecteurs pressés), pas trop le temps de réfléchir non plus hélas, pas le temps de suivre la ribambelle de liens associés à un article…<br /></em></p>\n<p><em>Pour nous donner bonne conscience, nous rangeons scrupuleusement un marque-page de plus dans un sous-dossier qui en comporte déjà 256, nous notons un élément de plus dans la toujours ridiculement longue toudouliste, bref nous remettons à plus tard, c’est-à-dire le plus souvent aux introuvables calendes grecques, le soin de lire vraiment un article jugé intéressant, de regarder une vidéo signalée par les rézossocios, de lire un chapitre entier d’un ouvrage disponible en ligne…<br /></em></p>\n<p><em>Alors bien sûr, à défaut de nous donner tout le temps qui serait nécessaire, des solutions existent pour nous permettre de « lire plus tard » en sauvegardant le précieux pollen de nos butinages de site en site, et d’en faire ultérieurement votre miel ; c’est bel et bon mais les ruches sont un peu distantes, ça s’appelle le cloud (nos amis techies m’ont bien expliqué mais j’ai seulement compris que des trucs à moi sont sur des machines lointaines, ça ne me rassure pas trop) et elles sont souvent propriétaires, ne laissant entrer que les <s>utilisateurs</s> consommateurs payants et qui consentent à leurs conditions. Sans compter que de gros bourdons viennent profiter plus ou moins discrètement de toutes ces traces de nous-mêmes qui permettent de monétiser notre profil : si je collecte sur ces services (ne les nommons pas, justement) une série d’articles sur l’idée de Nature chez Diderot, je recevrai diverses sollicitations pour devenir client de la boutique Nature &amp; Découverte du boulevard Diderot. Et si d’aventure les programmes de la NSA moulinent sur le service, je serai peut-être un jour dans une liste des militants naturistes indésirables sur les vols de la PanAm (je ne sais plus trop si je plaisante là, finalement…)<br /></em></p>\n<p><em>La bonne idée : « se constituer un réservoir de documents sélectionnés à parcourir plus tard » appelait donc une autre bonne idée, celle d’avoir le contrôle de ce réservoir, de notre collection personnelle. C’est Nicolas Lœuillet, ci-dessous interviewé, qui s’y est collé avec une belle application appelée euh… oui, appelée Wallabag.<br /></em></p>\n<p><em>Framasoft soutient d’autant plus son initiative qu’<a href=\"http://www.framablog.org/index.php/post/2014/01/31/Geektionnerd-Wallabag\">on lui a cherché des misères pour une histoire de nom</a> et qu’il est possible d’installer soi-même une copie de Wallabag sur son propre site.<br /></em></p>\n<p><em>Le petit plus de Framasoft, réseau toujours désireux de vous proposer des alternatives libératrices, c’est de vous proposer (sur inscription préalable) un accès au Framabag, autrement dit votre Wallabag sur un serveur Frama* avec notre garantie de confidentialité. Comme pour le Framanews, nous vous accueillons volontiers dans la limite de nos capacités, en vous invitant à vous lancer dans votre auto-hébergement de Wallabag.<br />Cet article est trop long ? Mettez-le dans <a href=\"http://www.framabag.org/index.php\">votre Framabag</a> et hop.<br /></em></p>\n<p><em>Framablog : Salut Nicolas… Tu peux te présenter brièvement ?<br /></em></p>\n<p>Salut ! Développeur PHP depuis quelques années maintenant (10 ans), j’ai voulu me remettre à niveau techniquement parlant (depuis 3 ans, j’ai pas mal lâché le clavier). Pour mes besoins persos, j’ai donc créé un petit projet pour remplacer une solution propriétaire existante. Sans aucune prétention, j’ai hébergé ce projet sur Github et comme c’est la seule solution <em>open source</em> de ce type, le nombre de personnes intéressées a augmenté …</p>\n<p><em>Les utilisateurs de services Framasoft ne le savent pas forcément, mais tu as déjà pas mal participé à la FramaGalaxie, non ?<br /></em></p>\n<p>En effet. J’ai commencé un plugin pour Framanews, <a href=\"https://github.com/nicosomb/ttrss-purge-accounts\">ttrss-purge-accounts</a>, qui permet de nettoyer la base de données de comptes plus utilisés. Mais ce <em>plugin</em> a besoin d’être terminé à 100% pour être intégré au sein de Framanews (et donc de Tiny Tiny RSS), si quelqu’un souhaite m’aider, il n’y a aucun souci.<br />J’ai aussi fait 1 ou 2 apparitions dans des traductions pour Framablog. Rien d’extraordinaire, je ne suis pas bilingue, ça me permet de m’entraîner.</p>\n<p><em>Parlons de suite de ce qui fâche : ton application Wallabag, elle s’appellait pas “Poche”, avant ? Tu nous racontes l’histoire ?<br /></em></p>\n<p>Euh en effet … Déjà, pourquoi <em>poche</em> ? Parce que l’un des trois « ténors » sur le marché s’appelle <em>Pocket</em>. Comme mon appli n’était destinée qu’à mon usage personnel au départ, je ne me suis pas torturé bien longtemps.</p>\n<p>Cet été, on a failli changer de nom, quand il y a eu de plus en plus d’utilisateurs. Et puis on s’est dit que poche, c’était pas mal, ça sonnait bien français et puis avec les quelques dizaines d’utilisateurs, on ne gênerait personne.</p>\n<p>C’est sans compter avec les sociétés américaines et leur fâcheuse manie de vouloir envoyer leurs avocats à tout bout de champ. Le 23 janvier, j’ai reçu un email de la part du cabinet d’avocats de Pocket me demandant de changer le nom, le logo, de ne plus utiliser le terme “read-it-later” (« lisez le plus tard ») et de ne plus dire que Pocket n’est pas gratuit (tout est parti d’<a href=\"https://twitter.com/wallabagapp/status/423786365944225792\">un tweet</a> où je qualifie Pocket de « non free » à savoir non libre). Bref, même si je semblais dans mon droit, j’ai quand même pris la décision de changer de nom et Wallabag est né, suite aux <a href=\"http://framadate.org/studs.php?sondage=llcp6ojpyc9pklha\">dizaines de propositions de nom reçues</a>. C’est un mélange entre le wallaby (de la famille des kangourous, qui stockent dans leur poche ce qui leur est cher) et <em>bag</em> (les termes sac / sacoche / besace sont énormément revenus). Mais maintenant, on va de l’avant, plus de temps à perdre avec ça, on a du pain sur la planche.<br /><img src=\"http://www.framablog.org/public/_img/framablog/wallaby_baby.jpg\" alt=\"wallaby avec bébé dans sa poche\" class=\"c1\" title=\"wallaby avec bébé dans sa poche\" /> crédit photo <a href=\"http://www.flickr.com/photos/26782864@N00/5027202234/in/photolist-8EeJ5A-h1TL6v-NEL81-cnNkSo-9YM1tv-7Kcg6b-8zpAoa-a1ZLMN-9YM39r-7h5SAD-8EeHfL-8EeFwu-dtVwnM-8uoME1-JEzXe-Gq4qy-92VJPR-Cxe1v-8H3D2J-a1ZFNs-9Y72K6-8EeGxL-5L53Fx-5NkENs-5U8CTY-5Nkssh-nkavF-9CrgwP-7sdCAa-duf2Kh-hZepzy-hZdU1e-hZeofF-hZekDg-hZegAY-hZeMZn-jaHgAf-8P87D2-5NgqRv-aT48QB-hZdV4Y-hZeC64-ERgps-5VYGGd-5VYJB1-5NkrFk-6Jxh7h-7h9PuQ-ERfMx-h1U1ih-h1USBx\">William Warby</a> qui autorise explicitement toute réutilisation.</p>\n<p><em>Bon, alors explique-moi ce que je vais pouvoir faire avec Framabag…<br /></em></p>\n<p>Alors Framabag, ça te permet de te créer un compte gratuitement et librement pour pouvoir utiliser Wallabag. Seule ton adresse email est nécessaire, on se charge d’installer et de mettre à jour Wallabag pour toi. Tu peux d’ailleurs profiter <a href=\"http://www.framasoft.net/#topPgCloud\">d’autres services proposés par Framasoft ici</a>.</p>\n<p>À ce jour, il y a 834 comptes créés sur Framabag.</p>\n<p><em>Vous avez vraiment conçu ce service afin qu’on puisse l’utiliser avec un maximum d’outils, non ?<br /></em></p>\n<p>Autour de l’application web, il existe déjà des applications pour smartphones (Android et Windows Phone), des extensions Firefox et Google Chrome.</p>\n<p>Comme Wallabag possède des flux RSS, c’est facile de lire les articles sauvegardés sur sa liseuse (si celle-ci permet de lire des flux RSS). Calibre (« logiciel de lecture, de gestion de bibliothèques et de conversion de fichiers numériques de type ebook ou livre électronique »,nous dit ubuntu-fr.org) intègre depuis quelques semaines maintenant la possibilité de récupérer les articles non lus, pratique pour faire un fichier ePub !</p>\n<p>D’autres applications web permettent l’intégration avec Wallabag (FreshRSS, Leed et Tiny Tiny RSS pour les agrégateurs de flux). L’API qui sera disponible dans la prochaine version de Wallabag permettra encore plus d’interactivité.</p>\n<p><em>Y a-t-il un mode de lecture hors ligne ou est-ce que c’est prévu pour les prochaines versions ?<br /></em></p>\n<p>Il y a un pseudo mode hors ligne, disponible avec l’application Android. On peut récupérer (via un flux RSS) les articles non lus que l’on a sauvegardés. Une fois déconnecté, on peut continuer à lire sur son smartphone ou sa tablette les articles. Par contre, il manque des fonctionnalités : quand tu marques un article comme lu, ce n’est pas synchronisé avec la version web de Wallabag. J’espère que je suis presque clair dans mes explications.</p>\n<p>Pour la v2, qui est déjà en cours de développement, où je suis bien aidé par Vincent Jousse, on aura la possibilité d’avoir un vrai mode hors ligne.</p>\n<p><em>Alors si on veut aider / participer / trifouiller le code / vous envoyer des retours, on fait comment ?<br /></em></p>\n<p>On peut aider de plusieurs façons :</p>\n<ul><li>utiliser wallabag et nous remonter les problèmes rencontrés ;</li>\n<li>participer au développement de l’application https://github.com/wallabag/wallabag Si Silex / Symfony2 / HTML5 / etc. te parlent, n’hésite pas !</li>\n<li>comme tout projet, le gros point noir est le manque de documentation. <a href=\"http://doc.wallabag.org\">Elle est dispo ici</a> mais il manque plein de choses et tout n’est pas à jour ;</li>\n<li>parler de Wallabag autour de vous ;</li>\n<li>il existe <a href=\"https://flattr.com/thing/1265480/poche-a-read-it-later-open-source-system\">un compte Flattr</a>.</li>\n</ul><p><em>Le mot de la fin…?<br /></em></p>\n<p>Merci à Framasoft d’accueillir et de soutenir Wallabag !</p>\n<p>La route est encore bien longue pour ne plus utiliser de solutions propriétaires, mais on devrait y arriver, non ?</p>\n<p><img src=\"http://www.framablog.org/public/_img/framablog/pleinLesPoches.png\" alt=\"framasoft plein les poches\" class=\"c1\" title=\"framasoft plein les poches\" /><br /><a href=\"http://framalab.org/gknd-creator/\">hackez Gégé !</a></p>\n",
16 "user_id": "1"
17 },
18 {
19 "0": "2",
20 "1": "wallabag/wallabag",
21 "2": "https://github.com/wallabag/wallabag",
22 "3": "1",
23 "4": "0",
24 "5": "<span class=\"name\">README.md</span><p>wallabag is a self hostable application allowing you to not miss any content anymore. Click, save, read it when you can. It extracts content so that you can read it when you have time.</p>\n<p>More informations on our website: <a href=\"http://wallabag.org\">wallabag.org</a></p>\n<h2><a class=\"anchor\" href=\"https://github.com/wallabag/wallabag#license\"></a>License</h2>\n<p>Copyright © 2010-2014 Nicolas Lœuillet <a href=\"mailto:nicolas@loeuillet.org\">nicolas@loeuillet.org</a> This work is free. You can redistribute it and/or modify it under the terms of the Do What The Fuck You Want To Public License, Version 2, as published by Sam Hocevar. See the COPYING file for more details.</p>\n",
25 "6": "1",
26 "id": "2",
27 "title": "wallabag/wallabag",
28 "url": "https://github.com/wallabag/wallabag",
29 "is_read": "1",
30 "is_fav": "0",
31 "content": "<span class=\"name\">README.md</span><p>wallabag is a self hostable application allowing you to not miss any content anymore. Click, save, read it when you can. It extracts content so that you can read it when you have time.</p>\n<p>More informations on our website: <a href=\"http://wallabag.org\">wallabag.org</a></p>\n<h2><a class=\"anchor\" href=\"https://github.com/wallabag/wallabag#license\"></a>License</h2>\n<p>Copyright © 2010-2014 Nicolas Lœuillet <a href=\"mailto:nicolas@loeuillet.org\">nicolas@loeuillet.org</a> This work is free. You can redistribute it and/or modify it under the terms of the Do What The Fuck You Want To Public License, Version 2, as published by Sam Hocevar. See the COPYING file for more details.</p>\n",
32 "user_id": "1"
33 },
34 {
35 "0": "3",
36 "1": "a self hostable application for saving web pages | wallabag",
37 "2": "https://www.wallabag.org/",
38 "3": "1",
39 "4": "0",
40 "5": "\n<div class=\"row\">\n<div class=\"col-lg-8 col-md-12 col-xs-12 col-sm-12\">\n<p>wallabag (formerly poche) is a <strong>self hostable application for saving web pages</strong>. Unlike other services, wallabag is free (as in freedom) and open source.</p>\n</div>\n\n</div>\n<div class=\"row\">\n<div class=\"col-lg-8 col-md-12 col-xs-12 col-sm-12\">\n<p>With this application you will not miss content anymore. <strong>Click, save, read it when you want</strong>. It saves the content you select so that you can read it when you have time.</p>\n</div>\n\n</div>\n<div class=\"row\">\n<div class=\"col-lg-6 col-md-12 col-xs-12 col-sm-12\">\n<h2>How it works</h2>\n<p>Thanks to the bookmarklet or <a title=\"Downloads\" href=\"http://www.wallabag.org/downloads/\">third-party applications</a>, you save an article in your wallabag to read it later. Then, when you open your wallabag, <strong>you can comfortably read your articles</strong>.</p>\n<h2>How to use wallabag</h2>\n<p>There are two ways to use wallabag: you can <a href=\"http://www.wallabag.org/frequently-asked-questions/#How_can_I_install_wallabag_and_what_are_the_requirements\">install it</a> on your web server or you can <a href=\"http://app.inthepoche.com\">create an account</a> at Framabag (we install and upgrade wallabag for you).</p>\n</div>\n\n</div>\n",
41 "6": "1",
42 "id": "3",
43 "title": "a self hostable application for saving web pages | wallabag",
44 "url": "https://www.wallabag.org/",
45 "is_read": "1",
46 "is_fav": "0",
47 "content": "\n<div class=\"row\">\n<div class=\"col-lg-8 col-md-12 col-xs-12 col-sm-12\">\n<p>wallabag (formerly poche) is a <strong>self hostable application for saving web pages</strong>. Unlike other services, wallabag is free (as in freedom) and open source.</p>\n</div>\n\n</div>\n<div class=\"row\">\n<div class=\"col-lg-8 col-md-12 col-xs-12 col-sm-12\">\n<p>With this application you will not miss content anymore. <strong>Click, save, read it when you want</strong>. It saves the content you select so that you can read it when you have time.</p>\n</div>\n\n</div>\n<div class=\"row\">\n<div class=\"col-lg-6 col-md-12 col-xs-12 col-sm-12\">\n<h2>How it works</h2>\n<p>Thanks to the bookmarklet or <a title=\"Downloads\" href=\"http://www.wallabag.org/downloads/\">third-party applications</a>, you save an article in your wallabag to read it later. Then, when you open your wallabag, <strong>you can comfortably read your articles</strong>.</p>\n<h2>How to use wallabag</h2>\n<p>There are two ways to use wallabag: you can <a href=\"http://www.wallabag.org/frequently-asked-questions/#How_can_I_install_wallabag_and_what_are_the_requirements\">install it</a> on your web server or you can <a href=\"http://app.inthepoche.com\">create an account</a> at Framabag (we install and upgrade wallabag for you).</p>\n</div>\n\n</div>\n",
48 "user_id": "1"
49 }
50]