aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/Wallabag/ImportBundle
diff options
context:
space:
mode:
authorThomas Citharel <tcit@tcit.fr>2016-07-12 13:51:05 +0200
committerJeremy Benoist <jeremy.benoist@gmail.com>2016-09-25 12:28:54 +0200
commitae669126e718ede5dbf76929215d8514cd960976 (patch)
treef7da9fa833014eaec0cc2cfade514df2cc117188 /src/Wallabag/ImportBundle
parent9d7dd6b0d2480d3efff5b0ab1461f2ef99bfd57a (diff)
downloadwallabag-ae669126e718ede5dbf76929215d8514cd960976.tar.gz
wallabag-ae669126e718ede5dbf76929215d8514cd960976.tar.zst
wallabag-ae669126e718ede5dbf76929215d8514cd960976.zip
Import Firefox & Chrome bookmarks into wallabag
Diffstat (limited to 'src/Wallabag/ImportBundle')
-rw-r--r--src/Wallabag/ImportBundle/Command/ImportCommand.php19
-rw-r--r--src/Wallabag/ImportBundle/Controller/BrowserController.php91
-rw-r--r--src/Wallabag/ImportBundle/Import/BrowserImport.php227
-rw-r--r--src/Wallabag/ImportBundle/Resources/config/services.yml10
-rw-r--r--src/Wallabag/ImportBundle/Resources/views/Browser/index.html.twig43
-rw-r--r--src/Wallabag/ImportBundle/Resources/views/Import/index.html.twig2
6 files changed, 386 insertions, 6 deletions
diff --git a/src/Wallabag/ImportBundle/Command/ImportCommand.php b/src/Wallabag/ImportBundle/Command/ImportCommand.php
index 20ecc6e1..537dffc2 100644
--- a/src/Wallabag/ImportBundle/Command/ImportCommand.php
+++ b/src/Wallabag/ImportBundle/Command/ImportCommand.php
@@ -17,7 +17,7 @@ class ImportCommand extends ContainerAwareCommand
17 ->setDescription('Import entries from a JSON export from a wallabag v1 instance') 17 ->setDescription('Import entries from a JSON export from a wallabag v1 instance')
18 ->addArgument('userId', InputArgument::REQUIRED, 'User ID to populate') 18 ->addArgument('userId', InputArgument::REQUIRED, 'User ID to populate')
19 ->addArgument('filepath', InputArgument::REQUIRED, 'Path to the JSON file') 19 ->addArgument('filepath', InputArgument::REQUIRED, 'Path to the JSON file')
20 ->addOption('importer', null, InputArgument::OPTIONAL, 'The importer to use: v1 or v2', 'v1') 20 ->addOption('importer', null, InputArgument::OPTIONAL, 'The importer to use: wallabag v1, v2 or browser', 'v1')
21 ->addOption('markAsRead', null, InputArgument::OPTIONAL, 'Mark all entries as read', false) 21 ->addOption('markAsRead', null, InputArgument::OPTIONAL, 'Mark all entries as read', false)
22 ; 22 ;
23 } 23 }
@@ -40,10 +40,19 @@ class ImportCommand extends ContainerAwareCommand
40 throw new Exception(sprintf('User with id "%s" not found', $input->getArgument('userId'))); 40 throw new Exception(sprintf('User with id "%s" not found', $input->getArgument('userId')));
41 } 41 }
42 42
43 $wallabag = $this->getContainer()->get('wallabag_import.wallabag_v1.import'); 43 switch ($input->getOption('importer')) {
44 44 case 'v2':
45 if ('v2' === $input->getOption('importer')) { 45 $wallabag = $this->getContainer()->get('wallabag_import.wallabag_v2.import');
46 $wallabag = $this->getContainer()->get('wallabag_import.wallabag_v2.import'); 46 break;
47 case 'v1':
48 $wallabag = $this->getContainer()->get('wallabag_import.wallabag_v1.import');
49 break;
50 case 'browser':
51 $wallabag = $this->getContainer()->get('wallabag_import.browser.import');
52 break;
53 default:
54 $wallabag = $this->getContainer()->get('wallabag_import.wallabag_v1.import');
55 break;
47 } 56 }
48 57
49 $wallabag->setMarkAsRead($input->getOption('markAsRead')); 58 $wallabag->setMarkAsRead($input->getOption('markAsRead'));
diff --git a/src/Wallabag/ImportBundle/Controller/BrowserController.php b/src/Wallabag/ImportBundle/Controller/BrowserController.php
new file mode 100644
index 00000000..3b54a72e
--- /dev/null
+++ b/src/Wallabag/ImportBundle/Controller/BrowserController.php
@@ -0,0 +1,91 @@
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 Symfony\Component\HttpFoundation\Response;
9use Wallabag\ImportBundle\Form\Type\UploadImportType;
10
11class BrowserController extends Controller
12{
13 /**
14 * Return the service to handle the import.
15 *
16 * @return \Wallabag\ImportBundle\Import\ImportInterface
17 */
18 protected function getImportService()
19 {
20 return $this->get('wallabag_import.browser.import');
21 }
22
23 /**
24 * Return the template used for the form.
25 *
26 * @return string
27 */
28 protected function getImportTemplate()
29 {
30 return 'WallabagImportBundle:Browser:index.html.twig';
31 }
32
33 /**
34 * @Route("/browser", name="import_browser")
35 *
36 * @param Request $request
37 *
38 * @return Response
39 */
40 public function indexAction(Request $request)
41 {
42 $form = $this->createForm(UploadImportType::class);
43 $form->handleRequest($request);
44
45 $wallabag = $this->getImportService();
46
47 if ($form->isValid()) {
48 $file = $form->get('file')->getData();
49 $markAsRead = $form->get('mark_as_read')->getData();
50 $name = $this->getUser()->getId().'.json';
51
52 if (in_array($file->getClientMimeType(), $this->getParameter('wallabag_import.allow_mimetypes')) && $file->move($this->getParameter('wallabag_import.resource_dir'), $name)) {
53 $res = $wallabag
54 ->setUser($this->getUser())
55 ->setFilepath($this->getParameter('wallabag_import.resource_dir').'/'.$name)
56 ->setMarkAsRead($markAsRead)
57 ->import();
58
59 $message = 'flashes.import.notice.failed';
60
61 if (true === $res) {
62 $summary = $wallabag->getSummary();
63 // TODO : Pluralize these messages
64 $message = $this->get('translator')->trans('flashes.import.notice.summary', [
65 '%imported%' => $summary['imported'],
66 '%skipped%' => $summary['skipped'],
67 ]);
68
69 unlink($this->getParameter('wallabag_import.resource_dir').'/'.$name);
70 }
71
72 $this->get('session')->getFlashBag()->add(
73 'notice',
74 $message
75 );
76
77 return $this->redirect($this->generateUrl('homepage'));
78 } else {
79 $this->get('session')->getFlashBag()->add(
80 'notice',
81 'flashes.import.notice.failed_on_file'
82 );
83 }
84 }
85
86 return $this->render($this->getImportTemplate(), [
87 'form' => $form->createView(),
88 'import' => $wallabag,
89 ]);
90 }
91}
diff --git a/src/Wallabag/ImportBundle/Import/BrowserImport.php b/src/Wallabag/ImportBundle/Import/BrowserImport.php
new file mode 100644
index 00000000..263a11d5
--- /dev/null
+++ b/src/Wallabag/ImportBundle/Import/BrowserImport.php
@@ -0,0 +1,227 @@
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\Helper\ContentProxy;
11
12class BrowserImport implements ImportInterface
13{
14 protected $user;
15 protected $em;
16 protected $logger;
17 protected $contentProxy;
18 protected $skippedEntries = 0;
19 protected $importedEntries = 0;
20 protected $totalEntries = 0;
21 protected $filepath;
22 protected $markAsRead;
23 private $nbEntries;
24
25 public function __construct(EntityManager $em, ContentProxy $contentProxy)
26 {
27 $this->em = $em;
28 $this->logger = new NullLogger();
29 $this->contentProxy = $contentProxy;
30 }
31
32 public function setLogger(LoggerInterface $logger)
33 {
34 $this->logger = $logger;
35 }
36
37 /**
38 * We define the user in a custom call because on the import command there is no logged in user.
39 * So we can't retrieve user from the `security.token_storage` service.
40 *
41 * @param User $user
42 *
43 * @return $this
44 */
45 public function setUser(User $user)
46 {
47 $this->user = $user;
48
49 return $this;
50 }
51
52 /**
53 * {@inheritdoc}
54 */
55 public function getName()
56 {
57 return 'Firefox & Google Chrome';
58 }
59
60 /**
61 * {@inheritdoc}
62 */
63 public function getUrl()
64 {
65 return 'import_browser';
66 }
67
68 /**
69 * {@inheritdoc}
70 */
71 public function getDescription()
72 {
73 return 'import.browser.description';
74 }
75
76 /**
77 * {@inheritdoc}
78 */
79 public function import()
80 {
81 if (!$this->user) {
82 $this->logger->error('WallabagImport: user is not defined');
83
84 return false;
85 }
86
87 if (!file_exists($this->filepath) || !is_readable($this->filepath)) {
88 $this->logger->error('WallabagImport: unable to read file', ['filepath' => $this->filepath]);
89
90 return false;
91 }
92
93 $data = json_decode(file_get_contents($this->filepath), true);
94
95 if (empty($data)) {
96 return false;
97 }
98
99 $this->nbEntries = 1;
100 $this->parseEntries($data);
101 $this->em->flush();
102
103 return true;
104 }
105
106 private function parseEntries($data)
107 {
108 foreach ($data as $importedEntry) {
109 $this->parseEntry($importedEntry);
110 }
111 $this->totalEntries += count($data);
112 }
113
114 private function parseEntry($importedEntry)
115 {
116 if (!is_array($importedEntry)) {
117 return;
118 }
119
120 /* Firefox uses guid while Chrome uses id */
121
122 if ((!key_exists('guid', $importedEntry) || (!key_exists('id', $importedEntry))) && is_array(reset($importedEntry))) {
123 $this->parseEntries($importedEntry);
124
125 return;
126 }
127 if (key_exists('children', $importedEntry)) {
128 $this->parseEntries($importedEntry['children']);
129
130 return;
131 }
132 if (key_exists('uri', $importedEntry) || key_exists('url', $importedEntry)) {
133
134 /* Firefox uses uri while Chrome uses url */
135
136 $firefox = key_exists('uri', $importedEntry);
137
138 $existingEntry = $this->em
139 ->getRepository('WallabagCoreBundle:Entry')
140 ->findByUrlAndUserId(($firefox) ? $importedEntry['uri'] : $importedEntry['url'], $this->user->getId());
141
142 if (false !== $existingEntry) {
143 ++$this->skippedEntries;
144
145 return;
146 }
147
148 if (false === parse_url(($firefox) ? $importedEntry['uri'] : $importedEntry['url']) || false === filter_var(($firefox) ? $importedEntry['uri'] : $importedEntry['url'], FILTER_VALIDATE_URL)) {
149 $this->logger->warning('Imported URL '.($firefox) ? $importedEntry['uri'] : $importedEntry['url'].' is not valid');
150 ++$this->skippedEntries;
151
152 return;
153 }
154
155 try {
156 $entry = $this->contentProxy->updateEntry(
157 new Entry($this->user),
158 ($firefox) ? $importedEntry['uri'] : $importedEntry['url']
159 );
160 } catch (\Exception $e) {
161 $this->logger->warning('Error while saving '.($firefox) ? $importedEntry['uri'] : $importedEntry['url']);
162 ++$this->skippedEntries;
163
164 return;
165 }
166
167 $entry->setArchived($this->markAsRead);
168
169 $this->em->persist($entry);
170 ++$this->importedEntries;
171
172 // flush every 20 entries
173 if (($this->nbEntries % 20) === 0) {
174 $this->em->flush();
175 $this->em->clear($entry);
176 }
177 ++$this->nbEntries;
178
179 /*
180
181 Maybe not useful. Delete at will.
182
183 */
184
185 $this->logger->info($this->nbEntries.' / '.$this->totalEntries);
186 }
187 }
188
189 /**
190 * Set whether articles must be all marked as read.
191 *
192 * @param bool $markAsRead
193 *
194 * @return $this
195 */
196 public function setMarkAsRead($markAsRead)
197 {
198 $this->markAsRead = $markAsRead;
199
200 return $this;
201 }
202
203 /**
204 * Set file path to the json file.
205 *
206 * @param string $filepath
207 *
208 * @return $this
209 */
210 public function setFilepath($filepath)
211 {
212 $this->filepath = $filepath;
213
214 return $this;
215 }
216
217 /**
218 * {@inheritdoc}
219 */
220 public function getSummary()
221 {
222 return [
223 'skipped' => $this->skippedEntries,
224 'imported' => $this->importedEntries,
225 ];
226 }
227}
diff --git a/src/Wallabag/ImportBundle/Resources/config/services.yml b/src/Wallabag/ImportBundle/Resources/config/services.yml
index f03404ae..d8be5c28 100644
--- a/src/Wallabag/ImportBundle/Resources/config/services.yml
+++ b/src/Wallabag/ImportBundle/Resources/config/services.yml
@@ -56,3 +56,13 @@ services:
56 - [ setLogger, [ "@logger" ]] 56 - [ setLogger, [ "@logger" ]]
57 tags: 57 tags:
58 - { name: wallabag_import.import, alias: readability } 58 - { name: wallabag_import.import, alias: readability }
59
60 wallabag_import.browser.import:
61 class: Wallabag\ImportBundle\Import\BrowserImport
62 arguments:
63 - "@doctrine.orm.entity_manager"
64 - "@wallabag_core.content_proxy"
65 calls:
66 - [ setLogger, [ "@logger" ]]
67 tags:
68 - { name: wallabag_import.import, alias: browser }
diff --git a/src/Wallabag/ImportBundle/Resources/views/Browser/index.html.twig b/src/Wallabag/ImportBundle/Resources/views/Browser/index.html.twig
new file mode 100644
index 00000000..bfc74e9d
--- /dev/null
+++ b/src/Wallabag/ImportBundle/Resources/views/Browser/index.html.twig
@@ -0,0 +1,43 @@
1{% extends "WallabagCoreBundle::layout.html.twig" %}
2
3{% block title %}{{ 'import.browser.page_title'|trans }}{% endblock %}
4
5{% block content %}
6<div class="row">
7 <div class="col s12">
8 <div class="card-panel settings">
9 <div class="row">
10 <blockquote>{{ import.description|trans|raw }}</blockquote>
11 <p>{{ 'import.browser.how_to'|trans }}</p>
12
13 <div class="col s12">
14 {{ form_start(form, {'method': 'POST'}) }}
15 {{ form_errors(form) }}
16 <div class="row">
17 <div class="file-field input-field col s12">
18 {{ form_errors(form.file) }}
19 <div class="btn">
20 <span>{{ form.file.vars.label|trans }}</span>
21 {{ form_widget(form.file) }}
22 </div>
23 <div class="file-path-wrapper">
24 <input class="file-path validate" type="text">
25 </div>
26 </div>
27 <div class="input-field col s6 with-checkbox">
28 <h6>{{ 'import.form.mark_as_read_title'|trans }}</h6>
29 {{ form_widget(form.mark_as_read) }}
30 {{ form_label(form.mark_as_read) }}
31 </div>
32 </div>
33
34 {{ form_widget(form.save, { 'attr': {'class': 'btn waves-effect waves-light'} }) }}
35
36 {{ form_rest(form) }}
37 </form>
38 </div>
39 </div>
40 </div>
41 </div>
42</div>
43{% endblock %}
diff --git a/src/Wallabag/ImportBundle/Resources/views/Import/index.html.twig b/src/Wallabag/ImportBundle/Resources/views/Import/index.html.twig
index aebbfa20..6ea5e0f4 100644
--- a/src/Wallabag/ImportBundle/Resources/views/Import/index.html.twig
+++ b/src/Wallabag/ImportBundle/Resources/views/Import/index.html.twig
@@ -11,7 +11,7 @@
11 {% for import in imports %} 11 {% for import in imports %}
12 <li> 12 <li>
13 <h5>{{ import.name }}</h5> 13 <h5>{{ import.name }}</h5>
14 <blockquote>{{ import.description|trans }}</blockquote> 14 <blockquote>{{ import.description|trans|raw }}</blockquote>
15 <p><a class="waves-effect waves-light btn" href="{{ path(import.url) }}">{{ 'import.action.import_contents'|trans }}</a></p> 15 <p><a class="waves-effect waves-light btn" href="{{ path(import.url) }}">{{ 'import.action.import_contents'|trans }}</a></p>
16 </li> 16 </li>
17 {% endfor %} 17 {% endfor %}