]> git.immae.eu Git - github/wallabag/wallabag.git/commitdiff
Import Firefox & Chrome bookmarks into wallabag
authorThomas Citharel <tcit@tcit.fr>
Tue, 12 Jul 2016 11:51:05 +0000 (13:51 +0200)
committerJeremy Benoist <jeremy.benoist@gmail.com>
Sun, 25 Sep 2016 10:28:54 +0000 (12:28 +0200)
19 files changed:
src/Wallabag/CoreBundle/Resources/translations/messages.da.yml
src/Wallabag/CoreBundle/Resources/translations/messages.de.yml
src/Wallabag/CoreBundle/Resources/translations/messages.en.yml
src/Wallabag/CoreBundle/Resources/translations/messages.es.yml
src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml
src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml
src/Wallabag/CoreBundle/Resources/translations/messages.it.yml
src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml
src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml
src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml
src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml
src/Wallabag/ImportBundle/Command/ImportCommand.php
src/Wallabag/ImportBundle/Controller/BrowserController.php [new file with mode: 0644]
src/Wallabag/ImportBundle/Import/BrowserImport.php [new file with mode: 0644]
src/Wallabag/ImportBundle/Resources/config/services.yml
src/Wallabag/ImportBundle/Resources/views/Browser/index.html.twig [new file with mode: 0644]
src/Wallabag/ImportBundle/Resources/views/Import/index.html.twig
tests/Wallabag/ImportBundle/Controller/BrowserControllerTest.php [new file with mode: 0644]
tests/Wallabag/ImportBundle/fixtures/Bookmarks [new file with mode: 0644]

index c6fcb355f70480279b6d79b8af9487031768ad98..f9f4a9586324e1a34ee121a921817f4b93f97e4f 100644 (file)
@@ -349,6 +349,10 @@ import:
     #     how_to: 'Please select your Readability export and click on the below button to upload and import it.'
     worker:
         # enabled: "Import is made asynchronously. Once the import task is started, an external worker will handle jobs one at a time. The current service is:"
+    # browser:
+    #    page_title: 'Import > Browser'
+    #    description: "This importer will import all your Firefox or Chrome bookmarks. <p>For Firefox, just go to your bookmarks (Ctrl+Maj+O), then into \"Import and backup\", choose \"Backup...\". You will obtain a .json file. </p>For Chrome, the location of the file depends on your operating system : <ul><li>On Linux, go into the <code>~/.config/chromium/Default/</code> directory</li><li>On Windows, it should be at <code>%LOCALAPPDATA%\\Google\\Chrome\\User Data\\Default</code></li><li>On OS X, it should be at ...</li></ul>Once you got there, copy the Bookmarks file someplace you'll find.<em><br>Note that if you have Chromium instead of Chrome, you'll have to correct paths accordingly.</em></p>"
+    #    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: 'Developer'
index c0e82b59b6533becd8aaafe2d730d404dddc4da5..c1196be584b09fb1af8808fb48ffd3490061d3f9 100644 (file)
@@ -349,6 +349,10 @@ import:
     #     how_to: 'Please select your Readability export and click on the below button to upload and import it.'
     worker:
         # enabled: "Import is made asynchronously. Once the import task is started, an external worker will handle jobs one at a time. The current service is:"
+    # browser:
+    #    page_title: 'Import > Browser'
+    #    description: "This importer will import all your Firefox or Chrome bookmarks. <p>For Firefox, just go to your bookmarks (Ctrl+Maj+O), then into \"Import and backup\", choose \"Backup...\". You will obtain a .json file. </p>For Chrome, the location of the file depends on your operating system : <ul><li>On Linux, go into the <code>~/.config/chromium/Default/</code> directory</li><li>On Windows, it should be at <code>%LOCALAPPDATA%\\Google\\Chrome\\User Data\\Default</code></li><li>On OS X, it should be at ...</li></ul>Once you got there, copy the Bookmarks file someplace you'll find.<em><br>Note that if you have Chromium instead of Chrome, you'll have to correct paths accordingly.</em></p>"
+    #    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: 'Entwickler'
index 6f262209af0f4f58f11d6406b3c0f24d9bc8fa93..99bd40799c39908f278be73c4bc0cba57dd9dac3 100644 (file)
@@ -349,6 +349,10 @@ import:
         how_to: 'Please select your Readability export and click on the below button to upload and import it.'
     worker:
         enabled: "Import is made asynchronously. Once the import task is started, an external worker will handle jobs one at a time. The current service is:"
+    browser:
+        page_title: 'Import > Browser'
+        description: "This importer will import all your Firefox or Chrome bookmarks. <p>For Firefox, just go to your bookmarks (Ctrl+Maj+O), then into \"Import and backup\", choose \"Backup...\". You will obtain a .json file. </p>For Chrome, the location of the file depends on your operating system : <ul><li>On Linux, go into the <code>~/.config/chromium/Default/</code> directory</li><li>On Windows, it should be at <code>%LOCALAPPDATA%\\Google\\Chrome\\User Data\\Default</code></li><li>On OS X, it should be at ...</li></ul>Once you got there, copy the Bookmarks file someplace you'll find.<em><br>Note that if you have Chromium instead of Chrome, you'll have to correct paths accordingly.</em></p>"
+        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: 'Developer'
index 7b9810697f4d9f1cfd6d8374ceaec0d9aa60a4e0..5ffeab076d990e541fbb5a3addf4dc25f0b89919 100644 (file)
@@ -349,6 +349,10 @@ import:
     #     how_to: 'Please select your Readability export and click on the below button to upload and import it.'
     worker:
         # enabled: "Import is made asynchronously. Once the import task is started, an external worker will handle jobs one at a time. The current service is:"
+    # browser:
+    #    page_title: 'Import > Browser'
+    #    description: "This importer will import all your Firefox or Chrome bookmarks. <p>For Firefox, just go to your bookmarks (Ctrl+Maj+O), then into \"Import and backup\", choose \"Backup...\". You will obtain a .json file. </p>For Chrome, the location of the file depends on your operating system : <ul><li>On Linux, go into the <code>~/.config/chromium/Default/</code> directory</li><li>On Windows, it should be at <code>%LOCALAPPDATA%\\Google\\Chrome\\User Data\\Default</code></li><li>On OS X, it should be at ...</li></ul>Once you got there, copy the Bookmarks file someplace you'll find.<em><br>Note that if you have Chromium instead of Chrome, you'll have to correct paths accordingly.</em></p>"
+    #    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: 'Promotor'
index 99fcc3781f2b71518d2dc6c02792cff00eab284b..fa2c3ca9330d6c02789be0e440281b8c2b6c9c3d 100644 (file)
@@ -349,6 +349,10 @@ import:
         # how_to: 'Please select your Readability export and click on the below button to upload and import it.'
     worker:
         # enabled: "Import is made asynchronously. Once the import task is started, an external worker will handle jobs one at a time. The current service is:"
+    # browser:
+    #    page_title: 'Import > Browser'
+    #    description: "This importer will import all your Firefox or Chrome bookmarks. <p>For Firefox, just go to your bookmarks (Ctrl+Maj+O), then into \"Import and backup\", choose \"Backup...\". You will obtain a .json file. </p>For Chrome, the location of the file depends on your operating system : <ul><li>On Linux, go into the <code>~/.config/chromium/Default/</code> directory</li><li>On Windows, it should be at <code>%LOCALAPPDATA%\\Google\\Chrome\\User Data\\Default</code></li><li>On OS X, it should be at ...</li></ul>Once you got there, copy the Bookmarks file someplace you'll find.<em><br>Note that if you have Chromium instead of Chrome, you'll have to correct paths accordingly.</em></p>"
+    #    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: 'Developer'
index dd82e7f59b26a737d40df278f75ee9d4784d6cef..b24cfa267016058df4e7dc7a589bac024815dcc7 100644 (file)
@@ -349,6 +349,10 @@ import:
         how_to: "Choisissez le fichier de votre export Readability et cliquez sur le bouton ci-dessous pour l'importer."
     worker:
         enabled: "Les imports sont asynchrones. Une fois l'import commencé un worker externe traitera les messages un par un. Le service activé est :"
+    browser:
+        page_title: 'Import > Navigateur'
+        description: "Cet outil va vous permettre d'importer tous vos marques-pages de Firefox ou de Google Chrome/Chromium. <p>Pour Firefox, ouvrez le panneau des marques-pages (Ctrl+Maj+O), puis dans « Importation et sauvegarde », choisissez « Sauvegarde... ». Vous allez récupérer un fichier .json. </p>Pour Google Chrome, la situation du fichier dépend de votre système d'exploitation : <ul><li>Sur GNU/Linux, allez dans le répertoire <code>~/.config/google-chrome/Default/</code></li><li>Sous Windows, il devrait se trouver à <code>%LOCALAPPDATA%\\Google\\Chrome\\User Data\\Default</code></li><li>Sur OS X, il devrait se trouver à...</li></ul>Une fois que vous y êtes, copiez le fichier Bookmarks à un endroit où vous le retrouverez.<em><br>Notez que si vous utilisez Chromium à la place de Chrome, vous devez corriger les chemins en conséquence.</em></p>"
+        how_to: "Choisissez le fichier de sauvegarde de vos marques-page et cliquez sur le bouton pour l'importer. Soyez avertis que le processus peut prendre un temps assez long car tous les articles doivent être récupérés en ligne."
 
 developer:
     page_title: 'Développeur'
@@ -432,7 +436,7 @@ flashes:
         notice:
             failed: "L'import a échoué, veuillez ré-essayer"
             failed_on_file: "Erreur lors du traitement de l'import. Vérifier votre fichier."
-            summary: "Rapport d'import: %imported% importés, %skipped% déjà présent."
+            summary: "Rapport d'import: %imported% importés, %skipped% déjà présents."
             summary_with_queue: "Rapport d'import: %queued% en cours de traitement."
         error:
             redis_enabled_not_installed: Redis est activé pour les imports asynchrones mais <u>impossible de s'y connecter</u>. Vérifier la configuration de Redis.
index d3ce30c9ef70140e9e7fe2a155126058ade8fddc..f6aa245e93451fd67d847bbf16967becdffc9ce8 100644 (file)
@@ -348,6 +348,10 @@ import:
         # how_to: 'Please select your Readability export and click on the below button to upload and import it.'
     worker:
         # enabled: "Import is made asynchronously. Once the import task is started, an external worker will handle jobs one at a time. The current service is:"
+    # browser:
+    #    page_title: 'Import > Browser'
+    #    description: "This importer will import all your Firefox or Chrome bookmarks. <p>For Firefox, go to your bookmarks (Ctrl+Maj+O), then into \"Import and backup\", choose \"Backup...\". You will obtain a .json file. </p>For Chrome, the location of the file depends on your operating system : <ul><li>On Linux, go into the .config/chromium/Default/ directory</li><li>On Windows, it should be at %LOCALAPPDATA%\Google\Chrome\User Data\Default</li>On OS X, it should be at ...</li></ul>Once you got there, copy the Bookmarks file someplace you'll find.<em>Note that you may have Chromium instead of Chrome and have to correct paths accordingly.</em></p>"
+    #    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: 'Sviluppatori'
index d040daea644b49f0cd2372da9ede7cf40c97e983..596c764fff9752bb2e8a4cf449bf24c6450b077e 100644 (file)
@@ -349,6 +349,10 @@ import:
         how_to: "Mercés de seleccionar vòstre Readability fichièr e de clicar sul boton dejós per lo telecargar e l'importar."
     worker:
         # enabled: "Import is made asynchronously. Once the import task is started, an external worker will handle jobs one at a time. The current service is:"
+    # browser:
+    #    page_title: 'Import > Browser'
+    #    description: "This importer will import all your Firefox or Chrome bookmarks. <p>For Firefox, just go to your bookmarks (Ctrl+Maj+O), then into \"Import and backup\", choose \"Backup...\". You will obtain a .json file. </p>For Chrome, the location of the file depends on your operating system : <ul><li>On Linux, go into the <code>~/.config/chromium/Default/</code> directory</li><li>On Windows, it should be at <code>%LOCALAPPDATA%\\Google\\Chrome\\User Data\\Default</code></li><li>On OS X, it should be at ...</li></ul>Once you got there, copy the Bookmarks file someplace you'll find.<em><br>Note that if you have Chromium instead of Chrome, you'll have to correct paths accordingly.</em></p>"
+    #    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: 'Desvolopador'
index a51ed1f2759f4b1b87e3b6eb52b2c3e971642186..bf0da0222edb254671aea5f70d2ecd20b81e3e1a 100644 (file)
@@ -349,6 +349,10 @@ import:
         how_to: 'Wybierz swój plik eksportu z Readability i kliknij poniższy przycisk, aby go załadować.'
     worker:
         enabled: "Import jest wykonywany asynchronicznie. Od momentu rozpoczęcia importu, zewnętrzna usługa może zajmować się na raz tylko jednym zadaniem. Bieżącą usługą jest:"
+    # browser:
+    #    page_title: 'Import > Browser'
+    #    description: "This importer will import all your Firefox or Chrome bookmarks. <p>For Firefox, just go to your bookmarks (Ctrl+Maj+O), then into \"Import and backup\", choose \"Backup...\". You will obtain a .json file. </p>For Chrome, the location of the file depends on your operating system : <ul><li>On Linux, go into the <code>~/.config/chromium/Default/</code> directory</li><li>On Windows, it should be at <code>%LOCALAPPDATA%\\Google\\Chrome\\User Data\\Default</code></li><li>On OS X, it should be at ...</li></ul>Once you got there, copy the Bookmarks file someplace you'll find.<em><br>Note that if you have Chromium instead of Chrome, you'll have to correct paths accordingly.</em></p>"
+    #    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: 'Deweloper'
index de21f0b35b4a9f1f7eb66d337872ac2b23dadc36..928589cbccf6b9018a561473c931403ca62c7b87 100644 (file)
@@ -349,6 +349,10 @@ import:
     #     how_to: 'Please select your Readability export and click on the below button to upload and import it.'
     worker:
         # enabled: "Import is made asynchronously. Once the import task is started, an external worker will handle jobs one at a time. The current service is:"
+    # browser:
+    #    page_title: 'Import > Browser'
+    #    description: "This importer will import all your Firefox or Chrome bookmarks. <p>For Firefox, just go to your bookmarks (Ctrl+Maj+O), then into \"Import and backup\", choose \"Backup...\". You will obtain a .json file. </p>For Chrome, the location of the file depends on your operating system : <ul><li>On Linux, go into the <code>~/.config/chromium/Default/</code> directory</li><li>On Windows, it should be at <code>%LOCALAPPDATA%\\Google\\Chrome\\User Data\\Default</code></li><li>On OS X, it should be at ...</li></ul>Once you got there, copy the Bookmarks file someplace you'll find.<em><br>Note that if you have Chromium instead of Chrome, you'll have to correct paths accordingly.</em></p>"
+    #    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: 'Developer'
index d4b7a7a2218ea1a83583c6d64d5310c6f606bff7..723b1edb04c6bcbb66fcf8a5d5be3d92e4106798 100644 (file)
@@ -349,6 +349,10 @@ import:
         # how_to: 'Please select your Readability export and click on the below button to upload and import it.'
     worker:
         # enabled: "Import is made asynchronously. Once the import task is started, an external worker will handle jobs one at a time. The current service is:"
+    # browser:
+    #    page_title: 'Import > Browser'
+    #    description: "This importer will import all your Firefox or Chrome bookmarks. <p>For Firefox, just go to your bookmarks (Ctrl+Maj+O), then into \"Import and backup\", choose \"Backup...\". You will obtain a .json file. </p>For Chrome, the location of the file depends on your operating system : <ul><li>On Linux, go into the <code>~/.config/chromium/Default/</code> directory</li><li>On Windows, it should be at <code>%LOCALAPPDATA%\\Google\\Chrome\\User Data\\Default</code></li><li>On OS X, it should be at ...</li></ul>Once you got there, copy the Bookmarks file someplace you'll find.<em><br>Note that if you have Chromium instead of Chrome, you'll have to correct paths accordingly.</em></p>"
+    #    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: 'Developer'
index 20ecc6e1893a09292df62209037e6f9e9ed709a4..537dffc207f480bf0112a2b249a457b6e5e7c673 100644 (file)
@@ -17,7 +17,7 @@ class ImportCommand extends ContainerAwareCommand
             ->setDescription('Import entries from a JSON export from a wallabag v1 instance')
             ->addArgument('userId', InputArgument::REQUIRED, 'User ID to populate')
             ->addArgument('filepath', InputArgument::REQUIRED, 'Path to the JSON file')
-            ->addOption('importer', null, InputArgument::OPTIONAL, 'The importer to use: v1 or v2', 'v1')
+            ->addOption('importer', null, InputArgument::OPTIONAL, 'The importer to use: wallabag v1, v2 or browser', 'v1')
             ->addOption('markAsRead', null, InputArgument::OPTIONAL, 'Mark all entries as read', false)
         ;
     }
@@ -40,10 +40,19 @@ class ImportCommand extends ContainerAwareCommand
             throw new Exception(sprintf('User with id "%s" not found', $input->getArgument('userId')));
         }
 
-        $wallabag = $this->getContainer()->get('wallabag_import.wallabag_v1.import');
-
-        if ('v2' === $input->getOption('importer')) {
-            $wallabag = $this->getContainer()->get('wallabag_import.wallabag_v2.import');
+        switch ($input->getOption('importer')) {
+            case 'v2':
+                $wallabag = $this->getContainer()->get('wallabag_import.wallabag_v2.import');
+                break;
+            case 'v1':
+                $wallabag = $this->getContainer()->get('wallabag_import.wallabag_v1.import');
+                break;
+            case 'browser':
+                $wallabag = $this->getContainer()->get('wallabag_import.browser.import');
+                break;
+            default:
+                $wallabag = $this->getContainer()->get('wallabag_import.wallabag_v1.import');
+                break;
         }
 
         $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 (file)
index 0000000..3b54a72
--- /dev/null
@@ -0,0 +1,91 @@
+<?php
+
+namespace Wallabag\ImportBundle\Controller;
+
+use Symfony\Bundle\FrameworkBundle\Controller\Controller;
+use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Wallabag\ImportBundle\Form\Type\UploadImportType;
+
+class BrowserController extends Controller
+{
+    /**
+     * Return the service to handle the import.
+     *
+     * @return \Wallabag\ImportBundle\Import\ImportInterface
+     */
+    protected function getImportService()
+    {
+        return $this->get('wallabag_import.browser.import');
+    }
+
+     /**
+      * Return the template used for the form.
+      *
+      * @return string
+      */
+     protected function getImportTemplate()
+     {
+         return 'WallabagImportBundle:Browser:index.html.twig';
+     }
+
+    /**
+     * @Route("/browser", name="import_browser")
+     *
+     * @param Request $request
+     *
+     * @return Response
+     */
+    public function indexAction(Request $request)
+    {
+        $form = $this->createForm(UploadImportType::class);
+        $form->handleRequest($request);
+
+        $wallabag = $this->getImportService();
+
+        if ($form->isValid()) {
+            $file = $form->get('file')->getData();
+            $markAsRead = $form->get('mark_as_read')->getData();
+            $name = $this->getUser()->getId().'.json';
+
+            if (in_array($file->getClientMimeType(), $this->getParameter('wallabag_import.allow_mimetypes')) && $file->move($this->getParameter('wallabag_import.resource_dir'), $name)) {
+                $res = $wallabag
+                    ->setUser($this->getUser())
+                    ->setFilepath($this->getParameter('wallabag_import.resource_dir').'/'.$name)
+                    ->setMarkAsRead($markAsRead)
+                    ->import();
+
+                $message = 'flashes.import.notice.failed';
+
+                if (true === $res) {
+                    $summary = $wallabag->getSummary();
+                    // TODO : Pluralize these messages
+                    $message = $this->get('translator')->trans('flashes.import.notice.summary', [
+                        '%imported%' => $summary['imported'],
+                        '%skipped%' => $summary['skipped'],
+                    ]);
+
+                    unlink($this->getParameter('wallabag_import.resource_dir').'/'.$name);
+                }
+
+                $this->get('session')->getFlashBag()->add(
+                    'notice',
+                    $message
+                );
+
+                return $this->redirect($this->generateUrl('homepage'));
+            } else {
+                $this->get('session')->getFlashBag()->add(
+                    'notice',
+                    'flashes.import.notice.failed_on_file'
+                );
+            }
+        }
+
+        return $this->render($this->getImportTemplate(), [
+            'form' => $form->createView(),
+            'import' => $wallabag,
+        ]);
+    }
+}
diff --git a/src/Wallabag/ImportBundle/Import/BrowserImport.php b/src/Wallabag/ImportBundle/Import/BrowserImport.php
new file mode 100644 (file)
index 0000000..263a11d
--- /dev/null
@@ -0,0 +1,227 @@
+<?php
+
+namespace Wallabag\ImportBundle\Import;
+
+use Psr\Log\LoggerInterface;
+use Psr\Log\NullLogger;
+use Doctrine\ORM\EntityManager;
+use Wallabag\CoreBundle\Entity\Entry;
+use Wallabag\UserBundle\Entity\User;
+use Wallabag\CoreBundle\Helper\ContentProxy;
+
+class BrowserImport implements ImportInterface
+{
+    protected $user;
+    protected $em;
+    protected $logger;
+    protected $contentProxy;
+    protected $skippedEntries = 0;
+    protected $importedEntries = 0;
+    protected $totalEntries = 0;
+    protected $filepath;
+    protected $markAsRead;
+    private $nbEntries;
+
+    public function __construct(EntityManager $em, ContentProxy $contentProxy)
+    {
+        $this->em = $em;
+        $this->logger = new NullLogger();
+        $this->contentProxy = $contentProxy;
+    }
+
+    public function setLogger(LoggerInterface $logger)
+    {
+        $this->logger = $logger;
+    }
+
+    /**
+     * We define the user in a custom call because on the import command there is no logged in user.
+     * So we can't retrieve user from the `security.token_storage` service.
+     *
+     * @param User $user
+     *
+     * @return $this
+     */
+    public function setUser(User $user)
+    {
+        $this->user = $user;
+
+        return $this;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getName()
+    {
+        return 'Firefox & Google Chrome';
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getUrl()
+    {
+        return 'import_browser';
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getDescription()
+    {
+        return 'import.browser.description';
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function import()
+    {
+        if (!$this->user) {
+            $this->logger->error('WallabagImport: user is not defined');
+
+            return false;
+        }
+
+        if (!file_exists($this->filepath) || !is_readable($this->filepath)) {
+            $this->logger->error('WallabagImport: unable to read file', ['filepath' => $this->filepath]);
+
+            return false;
+        }
+
+        $data = json_decode(file_get_contents($this->filepath), true);
+
+        if (empty($data)) {
+            return false;
+        }
+
+        $this->nbEntries = 1;
+        $this->parseEntries($data);
+        $this->em->flush();
+
+        return true;
+    }
+
+    private function parseEntries($data)
+    {
+        foreach ($data as $importedEntry) {
+            $this->parseEntry($importedEntry);
+        }
+        $this->totalEntries += count($data);
+    }
+
+    private function parseEntry($importedEntry)
+    {
+        if (!is_array($importedEntry)) {
+            return;
+        }
+
+        /* Firefox uses guid while Chrome uses id */
+
+        if ((!key_exists('guid', $importedEntry) || (!key_exists('id', $importedEntry))) && is_array(reset($importedEntry))) {
+            $this->parseEntries($importedEntry);
+
+            return;
+        }
+        if (key_exists('children', $importedEntry)) {
+            $this->parseEntries($importedEntry['children']);
+
+            return;
+        }
+        if (key_exists('uri', $importedEntry) || key_exists('url', $importedEntry)) {
+
+            /* Firefox uses uri while Chrome uses url */
+
+            $firefox = key_exists('uri', $importedEntry);
+
+            $existingEntry = $this->em
+                ->getRepository('WallabagCoreBundle:Entry')
+                ->findByUrlAndUserId(($firefox) ? $importedEntry['uri'] : $importedEntry['url'], $this->user->getId());
+
+            if (false !== $existingEntry) {
+                ++$this->skippedEntries;
+
+                return;
+            }
+
+            if (false === parse_url(($firefox) ? $importedEntry['uri'] : $importedEntry['url']) || false === filter_var(($firefox) ? $importedEntry['uri'] : $importedEntry['url'], FILTER_VALIDATE_URL)) {
+                $this->logger->warning('Imported URL '.($firefox) ? $importedEntry['uri'] : $importedEntry['url'].' is not valid');
+                ++$this->skippedEntries;
+
+                return;
+            }
+
+            try {
+                $entry = $this->contentProxy->updateEntry(
+                    new Entry($this->user),
+                    ($firefox) ? $importedEntry['uri'] : $importedEntry['url']
+                );
+            } catch (\Exception $e) {
+                $this->logger->warning('Error while saving '.($firefox) ? $importedEntry['uri'] : $importedEntry['url']);
+                ++$this->skippedEntries;
+
+                return;
+            }
+
+            $entry->setArchived($this->markAsRead);
+
+            $this->em->persist($entry);
+            ++$this->importedEntries;
+
+            // flush every 20 entries
+            if (($this->nbEntries % 20) === 0) {
+                $this->em->flush();
+                $this->em->clear($entry);
+            }
+            ++$this->nbEntries;
+
+            /*
+
+            Maybe not useful. Delete at will.
+
+            */
+
+            $this->logger->info($this->nbEntries.' / '.$this->totalEntries);
+        }
+    }
+
+    /**
+     * Set whether articles must be all marked as read.
+     *
+     * @param bool $markAsRead
+     *
+     * @return $this
+     */
+    public function setMarkAsRead($markAsRead)
+    {
+        $this->markAsRead = $markAsRead;
+
+        return $this;
+    }
+
+    /**
+     * Set file path to the json file.
+     *
+     * @param string $filepath
+     *
+     * @return $this
+     */
+    public function setFilepath($filepath)
+    {
+        $this->filepath = $filepath;
+
+        return $this;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getSummary()
+    {
+        return [
+            'skipped' => $this->skippedEntries,
+            'imported' => $this->importedEntries,
+        ];
+    }
+}
index f03404ae5cd4cc4b67f2b65b4e6201333c2e920e..d8be5c28f24ccdce5b336568b41dd88ff97c6130 100644 (file)
@@ -56,3 +56,13 @@ services:
             - [ setLogger, [ "@logger" ]]
         tags:
             -  { name: wallabag_import.import, alias: readability }
+
+    wallabag_import.browser.import:
+        class: Wallabag\ImportBundle\Import\BrowserImport
+        arguments:
+            - "@doctrine.orm.entity_manager"
+            - "@wallabag_core.content_proxy"
+        calls:
+            - [ setLogger, [ "@logger" ]]
+        tags:
+            -  { 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 (file)
index 0000000..bfc74e9
--- /dev/null
@@ -0,0 +1,43 @@
+{% extends "WallabagCoreBundle::layout.html.twig" %}
+
+{% block title %}{{ 'import.browser.page_title'|trans }}{% endblock %}
+
+{% block content %}
+<div class="row">
+    <div class="col s12">
+        <div class="card-panel settings">
+            <div class="row">
+                <blockquote>{{ import.description|trans|raw }}</blockquote>
+                <p>{{ 'import.browser.how_to'|trans }}</p>
+
+                <div class="col s12">
+                    {{ form_start(form, {'method': 'POST'}) }}
+                        {{ form_errors(form) }}
+                        <div class="row">
+                            <div class="file-field input-field col s12">
+                                {{ form_errors(form.file) }}
+                                <div class="btn">
+                                    <span>{{ form.file.vars.label|trans }}</span>
+                                    {{ form_widget(form.file) }}
+                                </div>
+                                <div class="file-path-wrapper">
+                                    <input class="file-path validate" type="text">
+                                </div>
+                            </div>
+                            <div class="input-field col s6 with-checkbox">
+                                <h6>{{ 'import.form.mark_as_read_title'|trans }}</h6>
+                                {{ form_widget(form.mark_as_read) }}
+                                {{ form_label(form.mark_as_read) }}
+                            </div>
+                        </div>
+
+                        {{ form_widget(form.save, { 'attr': {'class': 'btn waves-effect waves-light'} }) }}
+
+                        {{ form_rest(form) }}
+                    </form>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+{% endblock %}
index aebbfa208391d8787896269177c66cb5f457523f..6ea5e0f42243475912030746ccbca06fb250769c 100644 (file)
@@ -11,7 +11,7 @@
                 {% for import in imports %}
                     <li>
                         <h5>{{ import.name }}</h5>
-                        <blockquote>{{ import.description|trans }}</blockquote>
+                        <blockquote>{{ import.description|trans|raw }}</blockquote>
                         <p><a class="waves-effect waves-light btn" href="{{ path(import.url) }}">{{ 'import.action.import_contents'|trans }}</a></p>
                     </li>
                 {% endfor %}
diff --git a/tests/Wallabag/ImportBundle/Controller/BrowserControllerTest.php b/tests/Wallabag/ImportBundle/Controller/BrowserControllerTest.php
new file mode 100644 (file)
index 0000000..8016227
--- /dev/null
@@ -0,0 +1,94 @@
+<?php
+
+namespace Tests\Wallabag\ImportBundle\Controller;
+
+use Tests\Wallabag\CoreBundle\WallabagCoreTestCase;
+use Symfony\Component\HttpFoundation\File\UploadedFile;
+
+class BrowserControllerTest extends WallabagCoreTestCase
+{
+    public function testImportWallabag()
+    {
+        $this->logInAs('admin');
+        $client = $this->getClient();
+
+        $crawler = $client->request('GET', '/import/browser');
+
+        $this->assertEquals(200, $client->getResponse()->getStatusCode());
+        $this->assertEquals(1, $crawler->filter('form[name=upload_import_file] > button[type=submit]')->count());
+        $this->assertEquals(1, $crawler->filter('input[type=file]')->count());
+    }
+
+    public function testImportWallabagWithFile()
+    {
+        $this->logInAs('admin');
+        $client = $this->getClient();
+
+        $crawler = $client->request('GET', '/import/browser');
+        $form = $crawler->filter('form[name=upload_import_file] > button[type=submit]')->form();
+
+        $file = new UploadedFile(__DIR__.'/../fixtures/Bookmarks', 'Bookmarks');
+
+        $data = [
+            'upload_import_file[file]' => $file,
+        ];
+
+        $client->submit($form, $data);
+
+        $this->assertEquals(302, $client->getResponse()->getStatusCode());
+
+        $crawler = $client->followRedirect();
+
+        $this->assertGreaterThan(1, $body = $crawler->filter('body')->extract(['_text']));
+        $this->assertContains('flashes.import.notice.summary', $body[0]);
+
+        $content = $client->getContainer()
+            ->get('doctrine.orm.entity_manager')
+            ->getRepository('WallabagCoreBundle:Entry')
+            ->findByUrlAndUserId(
+                'http://lexpansion.lexpress.fr/high-tech/orange-offre-un-meilleur-reseau-mobile-que-bouygues-et-sfr-free-derriere_1811554.html',
+                $this->getLoggedInUserId()
+            );
+
+        $this->assertNotEmpty($content->getMimetype());
+        $this->assertNotEmpty($content->getPreviewPicture());
+        $this->assertNotEmpty($content->getLanguage());
+        $this->assertEquals(0, count($content->getTags()));
+
+        $content = $client->getContainer()
+            ->get('doctrine.orm.entity_manager')
+            ->getRepository('WallabagCoreBundle:Entry')
+            ->findByUrlAndUserId(
+                'http://stackoverflow.com/questions/15017163/parser-for-exported-bookmarks-html-file-of-google-chrome-and-mozilla-in-java',
+                $this->getLoggedInUserId()
+            );
+
+        $this->assertNotEmpty($content->getMimetype());
+        $this->assertNotEmpty($content->getPreviewPicture());
+        $this->assertEmpty($content->getLanguage());
+    }
+
+    public function testImportWallabagWithEmptyFile()
+    {
+        $this->logInAs('admin');
+        $client = $this->getClient();
+
+        $crawler = $client->request('GET', '/import/browser');
+        $form = $crawler->filter('form[name=upload_import_file] > button[type=submit]')->form();
+
+        $file = new UploadedFile(__DIR__.'/../fixtures/test.txt', 'test.txt');
+
+        $data = [
+            'upload_import_file[file]' => $file,
+        ];
+
+        $client->submit($form, $data);
+
+        $this->assertEquals(302, $client->getResponse()->getStatusCode());
+
+        $crawler = $client->followRedirect();
+
+        $this->assertGreaterThan(1, $body = $crawler->filter('body')->extract(['_text']));
+        $this->assertContains('flashes.import.notice.failed', $body[0]);
+    }
+}
diff --git a/tests/Wallabag/ImportBundle/fixtures/Bookmarks b/tests/Wallabag/ImportBundle/fixtures/Bookmarks
new file mode 100644 (file)
index 0000000..8b78b8a
--- /dev/null
@@ -0,0 +1,61 @@
+{
+   "checksum": "ef1e30cddf64cb94c63d7835640165be",
+   "roots": {
+      "bookmark_bar": {
+         "children": [ {
+            "date_added": "13112787540531997",
+            "id": "7",
+            "name": "Terrorisme: les sombres prédictions du directeur de la DGSI",
+            "type": "url",
+            "url": "http://www.lefigaro.fr/actualite-france/2016/07/12/01016-20160712ARTFIG00016-terrorisme-les-sombres-perspectives-de-patrick-calvar-directeur-de-la-dgsi.php"
+         } ],
+         "date_added": "13112787380480144",
+         "date_modified": "13112787542724942",
+         "id": "1",
+         "name": "Bookmarks bar",
+         "type": "folder"
+      },
+      "other": {
+         "children": [ {
+            "date_added": "13112787503900822",
+            "id": "6",
+            "name": "Parser for Exported Bookmarks HTML file of Google Chrome and Mozilla in Java - Stack Overflow",
+            "type": "url",
+            "url": "http://stackoverflow.com/questions/15017163/parser-for-exported-bookmarks-html-file-of-google-chrome-and-mozilla-in-java"
+         }, {
+            "children": [ {
+               "date_added": "13112787564443378",
+               "id": "9",
+               "name": "Orange offre un meilleur réseau mobile que Bouygues et SFR, Free derrière - L'Express L'Expansion",
+               "type": "url",
+               "url": "http://lexpansion.lexpress.fr/high-tech/orange-offre-un-meilleur-reseau-mobile-que-bouygues-et-sfr-free-derriere_1811554.html"
+            } ],
+            "date_added": "13112787556763735",
+            "date_modified": "13112794390493325",
+            "id": "8",
+            "name": "test",
+            "type": "folder"
+         }, {
+            "date_added": "13112794390493325",
+            "id": "12",
+            "name": "JSON Formatter & Validator",
+            "type": "url",
+            "url": "https://jsonformatter.curiousconcept.com/"
+         } ],
+         "date_added": "13112787380480151",
+         "date_modified": "13112794393509988",
+         "id": "2",
+         "name": "Other bookmarks",
+         "type": "folder"
+      },
+      "synced": {
+         "children": [  ],
+         "date_added": "13112787380480155",
+         "date_modified": "0",
+         "id": "3",
+         "name": "Mobile bookmarks",
+         "type": "folder"
+      }
+   },
+   "version": 1
+}