mirror of
https://github.com/wallabag/wallabag.git
synced 2024-11-26 11:01:04 +00:00
Merge pull request #1493 from wallabag/v2-pocket-import
v2 – 1st draft for Pocket import via API & Wallabag v1 import
This commit is contained in:
commit
39643c6b76
51 changed files with 1770 additions and 35 deletions
|
@ -31,6 +31,7 @@ class AppKernel extends Kernel
|
|||
new Stof\DoctrineExtensionsBundle\StofDoctrineExtensionsBundle(),
|
||||
new Scheb\TwoFactorBundle\SchebTwoFactorBundle(),
|
||||
new KPhoen\RulerZBundle\KPhoenRulerZBundle(),
|
||||
new Wallabag\ImportBundle\WallabagImportBundle(),
|
||||
);
|
||||
|
||||
if (in_array($this->getEnvironment(), array('dev', 'test'))) {
|
||||
|
|
|
@ -31,6 +31,10 @@ wallabag_core:
|
|||
fr: 'Français'
|
||||
de: 'Deutsch'
|
||||
|
||||
wallabag_import:
|
||||
allow_mimetypes: ['application/octet-stream', 'application/json', 'text/plain']
|
||||
resource_dir: "%kernel.root_dir%/../web/uploads/import"
|
||||
|
||||
# Twig Configuration
|
||||
twig:
|
||||
debug: "%kernel.debug%"
|
||||
|
|
|
@ -60,3 +60,6 @@ parameters:
|
|||
language: en
|
||||
from_email: no-reply@wallabag.org
|
||||
rss_limit: 50
|
||||
|
||||
# pocket import
|
||||
pocket_consumer_key: xxxxxxxx
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
wallabag_import:
|
||||
resource: "@WallabagImportBundle/Controller/"
|
||||
type: annotation
|
||||
prefix: /import
|
||||
|
||||
wallabag_api:
|
||||
resource: "@WallabagApiBundle/Resources/config/routing.yml"
|
||||
prefix: /
|
||||
|
|
|
@ -60,3 +60,6 @@ parameters:
|
|||
language: en_US
|
||||
from_email: no-reply@wallabag.org
|
||||
rss_limit: 50
|
||||
|
||||
# pocket import
|
||||
pocket_consumer_key: xxxxxxxx
|
||||
|
|
|
@ -60,3 +60,6 @@ parameters:
|
|||
language: en_US
|
||||
from_email: no-reply@wallabag.org
|
||||
rss_limit: 50
|
||||
|
||||
# pocket import
|
||||
pocket_consumer_key: xxxxxxxx
|
||||
|
|
|
@ -60,3 +60,6 @@ parameters:
|
|||
language: en_US
|
||||
from_email: no-reply@wallabag.org
|
||||
rss_limit: 50
|
||||
|
||||
# pocket import
|
||||
pocket_consumer_key: xxxxxxxx
|
||||
|
|
|
@ -59,7 +59,8 @@
|
|||
"scheb/two-factor-bundle": "~1.4.0",
|
||||
"grandt/phpepub": "~4.0",
|
||||
"wallabag/php-mobi": "~1.0.0",
|
||||
"kphoen/rulerz-bundle": "~0.10"
|
||||
"kphoen/rulerz-bundle": "~0.10",
|
||||
"guzzlehttp/guzzle": "^5.2.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/doctrine-fixtures-bundle": "~2.2.0",
|
||||
|
|
38
composer.lock
generated
38
composer.lock
generated
|
@ -4,8 +4,8 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"hash": "91da706ef4b39a73704c3e2154c1a227",
|
||||
"content-hash": "81a3c2c84d78471bfb526b2b572182f7",
|
||||
"hash": "fdba142656b2089b0e4cbddb45e2ad1f",
|
||||
"content-hash": "a233f851c52683783b6a42be707c52b1",
|
||||
"packages": [
|
||||
{
|
||||
"name": "behat/transliterator",
|
||||
|
@ -117,33 +117,33 @@
|
|||
},
|
||||
{
|
||||
"name": "doctrine/cache",
|
||||
"version": "v1.5.4",
|
||||
"version": "v1.6.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/doctrine/cache.git",
|
||||
"reference": "47cdc76ceb95cc591d9c79a36dc3794975b5d136"
|
||||
"reference": "f8af318d14bdb0eff0336795b428b547bd39ccb6"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/doctrine/cache/zipball/47cdc76ceb95cc591d9c79a36dc3794975b5d136",
|
||||
"reference": "47cdc76ceb95cc591d9c79a36dc3794975b5d136",
|
||||
"url": "https://api.github.com/repos/doctrine/cache/zipball/f8af318d14bdb0eff0336795b428b547bd39ccb6",
|
||||
"reference": "f8af318d14bdb0eff0336795b428b547bd39ccb6",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.2"
|
||||
"php": "~5.5|~7.0"
|
||||
},
|
||||
"conflict": {
|
||||
"doctrine/common": ">2.2,<2.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": ">=3.7",
|
||||
"phpunit/phpunit": "~4.8|~5.0",
|
||||
"predis/predis": "~1.0",
|
||||
"satooshi/php-coveralls": "~0.6"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.5.x-dev"
|
||||
"dev-master": "1.6.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
|
@ -183,7 +183,7 @@
|
|||
"cache",
|
||||
"caching"
|
||||
],
|
||||
"time": "2015-12-19 05:03:47"
|
||||
"time": "2015-12-31 16:37:02"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/collections",
|
||||
|
@ -981,17 +981,17 @@
|
|||
},
|
||||
{
|
||||
"name": "friendsofsymfony/rest-bundle",
|
||||
"version": "1.7.6",
|
||||
"version": "1.7.7",
|
||||
"target-dir": "FOS/RestBundle",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/FriendsOfSymfony/FOSRestBundle.git",
|
||||
"reference": "f95b2f141748e9a5e2ddae833f60c38417aee8c3"
|
||||
"reference": "c79b7e5df96e5581591ceb6a026bd4e5f9346de0"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/FriendsOfSymfony/FOSRestBundle/zipball/f95b2f141748e9a5e2ddae833f60c38417aee8c3",
|
||||
"reference": "f95b2f141748e9a5e2ddae833f60c38417aee8c3",
|
||||
"url": "https://api.github.com/repos/FriendsOfSymfony/FOSRestBundle/zipball/c79b7e5df96e5581591ceb6a026bd4e5f9346de0",
|
||||
"reference": "c79b7e5df96e5581591ceb6a026bd4e5f9346de0",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -1063,7 +1063,7 @@
|
|||
"keywords": [
|
||||
"rest"
|
||||
],
|
||||
"time": "2015-12-20 13:45:30"
|
||||
"time": "2015-12-29 16:02:50"
|
||||
},
|
||||
{
|
||||
"name": "friendsofsymfony/user-bundle",
|
||||
|
@ -1071,12 +1071,12 @@
|
|||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/FriendsOfSymfony/FOSUserBundle.git",
|
||||
"reference": "e39b040e272c72f0a090c67d802e1d3b2d0b0313"
|
||||
"reference": "e5e7a2b8984da8dfedaf44adc7e5f60a62ad280c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/FriendsOfSymfony/FOSUserBundle/zipball/e39b040e272c72f0a090c67d802e1d3b2d0b0313",
|
||||
"reference": "e39b040e272c72f0a090c67d802e1d3b2d0b0313",
|
||||
"url": "https://api.github.com/repos/FriendsOfSymfony/FOSUserBundle/zipball/e5e7a2b8984da8dfedaf44adc7e5f60a62ad280c",
|
||||
"reference": "e5e7a2b8984da8dfedaf44adc7e5f60a62ad280c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -1132,7 +1132,7 @@
|
|||
"keywords": [
|
||||
"User management"
|
||||
],
|
||||
"time": "2015-12-05 09:38:57"
|
||||
"time": "2015-12-28 18:02:43"
|
||||
},
|
||||
{
|
||||
"name": "gedmo/doctrine-extensions",
|
||||
|
|
|
@ -24,6 +24,7 @@ The main documentation for the site is organized into a couple sections:
|
|||
user/login
|
||||
user/configuration
|
||||
user/first_article
|
||||
user/import
|
||||
|
||||
user/organize
|
||||
user/filters
|
||||
|
|
39
docs/en/user/import.rst
Normal file
39
docs/en/user/import.rst
Normal file
|
@ -0,0 +1,39 @@
|
|||
Migrate to wallabag
|
||||
===================
|
||||
|
||||
From wallabag 1.x
|
||||
-----------------
|
||||
|
||||
Export your data from your wallabag 1.x
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
On your config page, click on ``JSON export`` in the ``Export your wallabag data`` section.
|
||||
|
||||
.. image:: ../../img/user/export_wllbg_1.png
|
||||
:alt: Export from wallabag 1.x
|
||||
:align: center
|
||||
|
||||
You will have a ``wallabag-export-1-1970-01-01.json`` file.
|
||||
|
||||
Import your data into wallabag 2.x
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Click on ``Import`` link in the menu, select your export file on your computer and import it.
|
||||
|
||||
.. image:: ../../img/user/import_wllbg.png
|
||||
:alt: Import from wallabag 1.x
|
||||
:align: center
|
||||
|
||||
All your wallabag 1.x articles will be imported.
|
||||
|
||||
From Pocket
|
||||
-----------
|
||||
|
||||
From Instapaper
|
||||
---------------
|
||||
|
||||
From Readability
|
||||
----------------
|
||||
|
||||
From HTML or JSON file
|
||||
----------------------
|
BIN
docs/img/user/export_wllbg_1.png
Normal file
BIN
docs/img/user/export_wllbg_1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
BIN
docs/img/user/import_wllbg.png
Normal file
BIN
docs/img/user/import_wllbg.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
|
@ -60,7 +60,7 @@ class WallabagRestController extends FOSRestController
|
|||
* }
|
||||
* )
|
||||
*
|
||||
* @return Entry
|
||||
* @return Response
|
||||
*/
|
||||
public function getEntriesAction(Request $request)
|
||||
{
|
||||
|
@ -101,7 +101,7 @@ class WallabagRestController extends FOSRestController
|
|||
* }
|
||||
* )
|
||||
*
|
||||
* @return Entry
|
||||
* @return Response
|
||||
*/
|
||||
public function getEntryAction(Entry $entry)
|
||||
{
|
||||
|
@ -124,7 +124,7 @@ class WallabagRestController extends FOSRestController
|
|||
* }
|
||||
* )
|
||||
*
|
||||
* @return Entry
|
||||
* @return Response
|
||||
*/
|
||||
public function postEntriesAction(Request $request)
|
||||
{
|
||||
|
@ -166,7 +166,7 @@ class WallabagRestController extends FOSRestController
|
|||
* }
|
||||
* )
|
||||
*
|
||||
* @return Entry
|
||||
* @return Response
|
||||
*/
|
||||
public function patchEntriesAction(Entry $entry, Request $request)
|
||||
{
|
||||
|
@ -211,7 +211,7 @@ class WallabagRestController extends FOSRestController
|
|||
* }
|
||||
* )
|
||||
*
|
||||
* @return Entry
|
||||
* @return Response
|
||||
*/
|
||||
public function deleteEntriesAction(Entry $entry)
|
||||
{
|
||||
|
@ -235,6 +235,8 @@ class WallabagRestController extends FOSRestController
|
|||
* {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
|
||||
* }
|
||||
* )
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function getEntriesTagsAction(Entry $entry)
|
||||
{
|
||||
|
@ -257,6 +259,8 @@ class WallabagRestController extends FOSRestController
|
|||
* {"name"="tags", "dataType"="string", "required"=false, "format"="tag1,tag2,tag3", "description"="a comma-separated list of tags."},
|
||||
* }
|
||||
* )
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function postEntriesTagsAction(Request $request, Entry $entry)
|
||||
{
|
||||
|
@ -286,6 +290,8 @@ class WallabagRestController extends FOSRestController
|
|||
* {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
|
||||
* }
|
||||
* )
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function deleteEntriesTagsAction(Entry $entry, Tag $tag)
|
||||
{
|
||||
|
@ -306,6 +312,8 @@ class WallabagRestController extends FOSRestController
|
|||
* Retrieve all tags.
|
||||
*
|
||||
* @ApiDoc()
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function getTagsAction()
|
||||
{
|
||||
|
@ -328,6 +336,8 @@ class WallabagRestController extends FOSRestController
|
|||
* {"name"="tag", "dataType"="integer", "requirement"="\w+", "description"="The tag"}
|
||||
* }
|
||||
* )
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function deleteTagAction(Tag $tag)
|
||||
{
|
||||
|
|
|
@ -11,7 +11,6 @@ use Symfony\Component\Console\Output\NullOutput;
|
|||
use Symfony\Component\Console\Question\Question;
|
||||
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
||||
use Symfony\Component\Console\Helper\Table;
|
||||
use Wallabag\UserBundle\Entity\User;
|
||||
use Wallabag\CoreBundle\Entity\Config;
|
||||
|
||||
class InstallCommand extends ContainerAwareCommand
|
||||
|
|
|
@ -48,6 +48,19 @@ class EntryController extends Controller
|
|||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isValid()) {
|
||||
// check for existing entry, if it exists, redirect to it with a message
|
||||
$existingEntry = $this->get('wallabag_core.entry_repository')
|
||||
->existByUrlAndUserId($entry->getUrl(), $this->getUser()->getId());
|
||||
|
||||
if (false !== $existingEntry) {
|
||||
$this->get('session')->getFlashBag()->add(
|
||||
'notice',
|
||||
'Entry already saved on '.$existingEntry['createdAt']->format('d-m-Y')
|
||||
);
|
||||
|
||||
return $this->redirect($this->generateUrl('view', array('id' => $existingEntry['id'])));
|
||||
}
|
||||
|
||||
$this->updateEntry($entry);
|
||||
$this->get('session')->getFlashBag()->add(
|
||||
'notice',
|
||||
|
|
|
@ -5,6 +5,7 @@ namespace Wallabag\CoreBundle\Entity;
|
|||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
use Wallabag\UserBundle\Entity\User;
|
||||
|
||||
/**
|
||||
* Config.
|
||||
|
@ -86,7 +87,7 @@ class Config
|
|||
/*
|
||||
* @param User $user
|
||||
*/
|
||||
public function __construct(\Wallabag\UserBundle\Entity\User $user)
|
||||
public function __construct(User $user)
|
||||
{
|
||||
$this->user = $user;
|
||||
$this->taggingRules = new ArrayCollection();
|
||||
|
@ -181,7 +182,7 @@ class Config
|
|||
*
|
||||
* @return Config
|
||||
*/
|
||||
public function setUser(\Wallabag\UserBundle\Entity\User $user = null)
|
||||
public function setUser(User $user = null)
|
||||
{
|
||||
$this->user = $user;
|
||||
|
||||
|
@ -225,7 +226,7 @@ class Config
|
|||
/**
|
||||
* Set rssLimit.
|
||||
*
|
||||
* @param string $rssLimit
|
||||
* @param int $rssLimit
|
||||
*
|
||||
* @return Config
|
||||
*/
|
||||
|
@ -239,7 +240,7 @@ class Config
|
|||
/**
|
||||
* Get rssLimit.
|
||||
*
|
||||
* @return string
|
||||
* @return int
|
||||
*/
|
||||
public function getRssLimit()
|
||||
{
|
||||
|
|
|
@ -245,7 +245,7 @@ class Entry
|
|||
/**
|
||||
* Set isArchived.
|
||||
*
|
||||
* @param string $isArchived
|
||||
* @param bool $isArchived
|
||||
*
|
||||
* @return Entry
|
||||
*/
|
||||
|
@ -259,7 +259,7 @@ class Entry
|
|||
/**
|
||||
* Get isArchived.
|
||||
*
|
||||
* @return string
|
||||
* @return bool
|
||||
*/
|
||||
public function isArchived()
|
||||
{
|
||||
|
@ -276,7 +276,7 @@ class Entry
|
|||
/**
|
||||
* Set isStarred.
|
||||
*
|
||||
* @param string $isStarred
|
||||
* @param bool $isStarred
|
||||
*
|
||||
* @return Entry
|
||||
*/
|
||||
|
@ -290,7 +290,7 @@ class Entry
|
|||
/**
|
||||
* Get isStarred.
|
||||
*
|
||||
* @return string
|
||||
* @return bool
|
||||
*/
|
||||
public function isStarred()
|
||||
{
|
||||
|
|
|
@ -223,4 +223,29 @@ class EntryRepository extends EntityRepository
|
|||
->getQuery()
|
||||
->getResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find an entry by its url and its owner.
|
||||
* If it exists, return the entry otherwise return false.
|
||||
*
|
||||
* @param $url
|
||||
* @param $userId
|
||||
*
|
||||
* @return array|bool
|
||||
*/
|
||||
public function existByUrlAndUserId($url, $userId)
|
||||
{
|
||||
$res = $this->createQueryBuilder('e')
|
||||
->select('e.id, e.createdAt')
|
||||
->where('e.url = :url')->setParameter('url', $url)
|
||||
->andWhere('e.user = :user_id')->setParameter('user_id', $userId)
|
||||
->getQuery()
|
||||
->getResult();
|
||||
|
||||
if (count($res)) {
|
||||
return current($res);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,6 +63,7 @@ services:
|
|||
- @wallabag_core.tag_repository
|
||||
- @wallabag_core.entry_repository
|
||||
|
||||
# repository as a service
|
||||
wallabag_core.entry_repository:
|
||||
class: Wallabag\CoreBundle\Repository\EntryRepository
|
||||
factory: [ @doctrine.orm.default_entity_manager, getRepository ]
|
||||
|
|
|
@ -13,6 +13,7 @@ archive: 'Lus'
|
|||
all: 'Tous les articles'
|
||||
tags: 'Tags'
|
||||
config: 'Configuration'
|
||||
import: 'Importer'
|
||||
howto: 'Aide'
|
||||
logout: 'Déconnexion'
|
||||
Filtered: 'Articles filtrés'
|
||||
|
@ -128,3 +129,14 @@ Download: 'Télécharger'
|
|||
Does this article appear wrong?: "Est-ce que cet article s'affiche mal ?"
|
||||
Problems?: 'Un problème ?'
|
||||
Edit title: "Modifier le titre"
|
||||
|
||||
# Import
|
||||
Welcome on wallabag importer. Please select your previous service that you want to migrate.: "Bienvenue dans l'outil de migration de wallabag. Choisissez ci-dessous le service depuis lequel vous souhaitez migrer."
|
||||
"This importer will import all your Pocket data. Pocket doesn't allow us to retrieve content from their service, so the readable content of each article will be re-fetched by wallabag.": "Cet outil va importer toutes vos données de Pocket. Pocket ne nous autorise pas à récupérer le contenu depuis leur service, donc wallabag doit reparcourir chaque article pour récupérer son contenu."
|
||||
"This importer will import all your wallabag v1 articles. On your config page, click on \"JSON export\" in the \"Export your wallabag data\" section. You will have a \"wallabag-export-1-xxxx-xx-xx.json\" file.": "Cet outil va importer toutes vos données de wallabag v1. Sur votre page de configuration de wallabag v1, cliquez sur \"Export JSON\" dans la section \"Exporter vos données de wallabag\". Vous allez récupérer un fichier \"wallabag-export-1-xxxx-xx-xx.json\"."
|
||||
"You can import your data from your Pocket account. You just have to click on the below button and authorize the application to connect to getpocket.com.": "Vous pouvez importer vos données depuis votre compte Pocket. Vous n'avez qu'à cliquer sur le bouton ci-dessous et à autoriser wallabag à se connecter à getpocket.com."
|
||||
Connect to Pocket and import data: Se connecter à Pocket et importer les données.
|
||||
Please select your wallabag export and click on the below button to upload and import it.: Choisissez le fichier de votre export wallabag v1 et cliquez sur le bouton ci-dessous pour l'importer.
|
||||
File: Fichier
|
||||
Upload file: Importer le fichier
|
||||
Import contents: "Importer les contenus"
|
|
@ -45,6 +45,7 @@
|
|||
<li class="bold border-bottom {% if currentRoute == 'all' %}active{% endif %}"><a class="waves-effect" href="{{ path('all') }}">{% trans %}all{% endtrans %}</a></li>
|
||||
<li class="bold border-bottom {% if currentRoute == 'tags' %}active{% endif %}"><a class="waves-effect" href="{{ path('tag') }}">{% trans %}tags{% endtrans %}</a></li>
|
||||
<li class="bold {% if currentRoute == 'config' %}active{% endif %}"><a class="waves-effect" href="{{ path('config') }}">{% trans %}config{% endtrans %}</a></li>
|
||||
<li class="bold {% if currentRoute == 'import' %}active{% endif %}"><a class="waves-effect" href="{{ path('import') }}">{% trans %}import{% endtrans %}</a></li>
|
||||
<li class="bold {% if currentRoute == 'howto' %}active{% endif %}"><a class="waves-effect" href="{{ path('howto') }}">{% trans %}howto{% endtrans %}</a></li>
|
||||
<li class="bold"><a class="waves-effect" class="icon icon-power" href="{{ path('fos_user_security_logout') }}" title="{% trans %}logout{% endtrans %}">{% trans %}logout{% endtrans %}</a></li>
|
||||
</ul>
|
||||
|
|
|
@ -500,4 +500,8 @@ footer [class^="icon-"]:hover, footer [class*=" icon-"]:hover {
|
|||
/* force height on non-input field in the settings page */
|
||||
div.settings div.input-field div, div.settings div.input-field ul {
|
||||
margin-top: 40px;
|
||||
}
|
||||
}
|
||||
/* but avoid to kill all file input */
|
||||
div.settings div.file-field div {
|
||||
margin-top: inherit;
|
||||
}
|
||||
|
|
53
src/Wallabag/ImportBundle/Command/ImportCommand.php
Normal file
53
src/Wallabag/ImportBundle/Command/ImportCommand.php
Normal file
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
namespace Wallabag\ImportBundle\Command;
|
||||
|
||||
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
|
||||
use Symfony\Component\Config\Definition\Exception\Exception;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class ImportCommand extends ContainerAwareCommand
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('wallabag:import-v1')
|
||||
->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')
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$output->writeln('Start : '.(new \DateTime())->format('d-m-Y G:i:s').' ---');
|
||||
|
||||
$em = $this->getContainer()->get('doctrine')->getManager();
|
||||
// Turning off doctrine default logs queries for saving memory
|
||||
$em->getConnection()->getConfiguration()->setSQLLogger(null);
|
||||
|
||||
$user = $em->getRepository('WallabagUserBundle:User')->findOneById($input->getArgument('userId'));
|
||||
|
||||
if (!is_object($user)) {
|
||||
throw new Exception(sprintf('User with id "%s" not found', $input->getArgument('userId')));
|
||||
}
|
||||
|
||||
$wallabag = $this->getContainer()->get('wallabag_import.wallabag_v1.import');
|
||||
$res = $wallabag
|
||||
->setUser($user)
|
||||
->setFilepath($input->getArgument('filepath'))
|
||||
->import();
|
||||
|
||||
if (true === $res) {
|
||||
$summary = $wallabag->getSummary();
|
||||
$output->writeln('<info>'.$summary['imported'].' imported</info>');
|
||||
$output->writeln('<comment>'.$summary['skipped'].' already saved</comment>');
|
||||
}
|
||||
|
||||
$em->clear();
|
||||
|
||||
$output->writeln('End : '.(new \DateTime())->format('d-m-Y G:i:s').' ---');
|
||||
}
|
||||
}
|
19
src/Wallabag/ImportBundle/Controller/ImportController.php
Normal file
19
src/Wallabag/ImportBundle/Controller/ImportController.php
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace Wallabag\ImportBundle\Controller;
|
||||
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
|
||||
|
||||
class ImportController extends Controller
|
||||
{
|
||||
/**
|
||||
* @Route("/", name="import")
|
||||
*/
|
||||
public function importAction()
|
||||
{
|
||||
return $this->render('WallabagImportBundle:Import:index.html.twig', [
|
||||
'imports' => $this->get('wallabag_import.chain')->getAll(),
|
||||
]);
|
||||
}
|
||||
}
|
66
src/Wallabag/ImportBundle/Controller/PocketController.php
Normal file
66
src/Wallabag/ImportBundle/Controller/PocketController.php
Normal file
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
|
||||
namespace Wallabag\ImportBundle\Controller;
|
||||
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
|
||||
|
||||
class PocketController extends Controller
|
||||
{
|
||||
/**
|
||||
* @Route("/pocket", name="import_pocket")
|
||||
*/
|
||||
public function indexAction()
|
||||
{
|
||||
return $this->render('WallabagImportBundle:Pocket:index.html.twig', [
|
||||
'import' => $this->get('wallabag_import.pocket.import'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/pocket/auth", name="import_pocket_auth")
|
||||
*/
|
||||
public function authAction()
|
||||
{
|
||||
$requestToken = $this->get('wallabag_import.pocket.import')
|
||||
->getRequestToken($this->generateUrl('import', [], true));
|
||||
|
||||
$this->get('session')->set('import.pocket.code', $requestToken);
|
||||
|
||||
return $this->redirect(
|
||||
'https://getpocket.com/auth/authorize?request_token='.$requestToken.'&redirect_uri='.$this->generateUrl('import_pocket_callback', [], true),
|
||||
301
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/pocket/callback", name="import_pocket_callback")
|
||||
*/
|
||||
public function callbackAction()
|
||||
{
|
||||
$message = 'Import failed, please try again.';
|
||||
$pocket = $this->get('wallabag_import.pocket.import');
|
||||
|
||||
// something bad happend on pocket side
|
||||
if (false === $pocket->authorize($this->get('session')->get('import.pocket.code'))) {
|
||||
$this->get('session')->getFlashBag()->add(
|
||||
'notice',
|
||||
$message
|
||||
);
|
||||
|
||||
return $this->redirect($this->generateUrl('import_pocket'));
|
||||
}
|
||||
|
||||
if (true === $pocket->import()) {
|
||||
$summary = $pocket->getSummary();
|
||||
$message = 'Import summary: '.$summary['imported'].' imported, '.$summary['skipped'].' already saved.';
|
||||
}
|
||||
|
||||
$this->get('session')->getFlashBag()->add(
|
||||
'notice',
|
||||
$message
|
||||
);
|
||||
|
||||
return $this->redirect($this->generateUrl('homepage'));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
|
||||
namespace Wallabag\ImportBundle\Controller;
|
||||
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Wallabag\ImportBundle\Form\Type\UploadImportType;
|
||||
|
||||
class WallabagV1Controller extends Controller
|
||||
{
|
||||
/**
|
||||
* @Route("/wallabag-v1", name="import_wallabag_v1")
|
||||
*/
|
||||
public function indexAction(Request $request)
|
||||
{
|
||||
$form = $this->createForm(new UploadImportType());
|
||||
$form->handleRequest($request);
|
||||
|
||||
$wallabag = $this->get('wallabag_import.wallabag_v1.import');
|
||||
|
||||
if ($form->isValid()) {
|
||||
$file = $form->get('file')->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)
|
||||
->import();
|
||||
|
||||
$message = 'Import failed, please try again.';
|
||||
if (true === $res) {
|
||||
$summary = $wallabag->getSummary();
|
||||
$message = 'Import summary: '.$summary['imported'].' imported, '.$summary['skipped'].' already saved.';
|
||||
|
||||
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',
|
||||
'Error while processing import. Please verify your import file.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->render('WallabagImportBundle:WallabagV1:index.html.twig', [
|
||||
'form' => $form->createView(),
|
||||
'import' => $wallabag,
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
namespace Wallabag\ImportBundle\DependencyInjection;
|
||||
|
||||
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
|
||||
use Symfony\Component\Config\Definition\ConfigurationInterface;
|
||||
|
||||
class Configuration implements ConfigurationInterface
|
||||
{
|
||||
public function getConfigTreeBuilder()
|
||||
{
|
||||
$treeBuilder = new TreeBuilder();
|
||||
$rootNode = $treeBuilder->root('wallabag_import');
|
||||
|
||||
$rootNode
|
||||
->children()
|
||||
->arrayNode('allow_mimetypes')
|
||||
->prototype('scalar')->end()
|
||||
->end()
|
||||
->scalarNode('resource_dir')
|
||||
->end()
|
||||
->end()
|
||||
;
|
||||
|
||||
return $treeBuilder;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
namespace Wallabag\ImportBundle\DependencyInjection;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\Config\FileLocator;
|
||||
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
|
||||
use Symfony\Component\DependencyInjection\Loader;
|
||||
|
||||
class WallabagImportExtension extends Extension
|
||||
{
|
||||
public function load(array $configs, ContainerBuilder $container)
|
||||
{
|
||||
$configuration = new Configuration();
|
||||
$config = $this->processConfiguration($configuration, $configs);
|
||||
$container->setParameter('wallabag_import.allow_mimetypes', $config['allow_mimetypes']);
|
||||
$container->setParameter('wallabag_import.resource_dir', $config['resource_dir']);
|
||||
|
||||
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
|
||||
$loader->load('services.yml');
|
||||
}
|
||||
|
||||
public function getAlias()
|
||||
{
|
||||
return 'wallabag_import';
|
||||
}
|
||||
}
|
22
src/Wallabag/ImportBundle/Form/Type/UploadImportType.php
Normal file
22
src/Wallabag/ImportBundle/Form/Type/UploadImportType.php
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
namespace Wallabag\ImportBundle\Form\Type;
|
||||
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
|
||||
class UploadImportType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$builder
|
||||
->add('file', 'file')
|
||||
->add('save', 'submit')
|
||||
;
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
return 'upload_import_file';
|
||||
}
|
||||
}
|
34
src/Wallabag/ImportBundle/Import/ImportChain.php
Normal file
34
src/Wallabag/ImportBundle/Import/ImportChain.php
Normal file
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace Wallabag\ImportBundle\Import;
|
||||
|
||||
class ImportChain
|
||||
{
|
||||
private $imports;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->imports = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an import to the chain.
|
||||
*
|
||||
* @param ImportInterface $import
|
||||
* @param string $alias
|
||||
*/
|
||||
public function addImport(ImportInterface $import, $alias)
|
||||
{
|
||||
$this->imports[$alias] = $import;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all imports.
|
||||
*
|
||||
* @return array<ImportInterface>
|
||||
*/
|
||||
public function getAll()
|
||||
{
|
||||
return $this->imports;
|
||||
}
|
||||
}
|
33
src/Wallabag/ImportBundle/Import/ImportCompilerPass.php
Normal file
33
src/Wallabag/ImportBundle/Import/ImportCompilerPass.php
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
namespace Wallabag\ImportBundle\Import;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
|
||||
class ImportCompilerPass implements CompilerPassInterface
|
||||
{
|
||||
public function process(ContainerBuilder $container)
|
||||
{
|
||||
if (!$container->hasDefinition('wallabag_import.chain')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$definition = $container->getDefinition(
|
||||
'wallabag_import.chain'
|
||||
);
|
||||
|
||||
$taggedServices = $container->findTaggedServiceIds(
|
||||
'wallabag_import.import'
|
||||
);
|
||||
foreach ($taggedServices as $id => $tagAttributes) {
|
||||
foreach ($tagAttributes as $attributes) {
|
||||
$definition->addMethodCall(
|
||||
'addImport',
|
||||
[new Reference($id), $attributes['alias']]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
45
src/Wallabag/ImportBundle/Import/ImportInterface.php
Normal file
45
src/Wallabag/ImportBundle/Import/ImportInterface.php
Normal file
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
namespace Wallabag\ImportBundle\Import;
|
||||
|
||||
use Psr\Log\LoggerAwareInterface;
|
||||
|
||||
interface ImportInterface extends LoggerAwareInterface
|
||||
{
|
||||
/**
|
||||
* Name of the import.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName();
|
||||
|
||||
/**
|
||||
* Url to start the import.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUrl();
|
||||
|
||||
/**
|
||||
* Description of the import.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription();
|
||||
|
||||
/**
|
||||
* Import content using the user token.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function import();
|
||||
|
||||
/**
|
||||
* Return an array with summary info about the import, with keys:
|
||||
* - skipped
|
||||
* - imported.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getSummary();
|
||||
}
|
267
src/Wallabag/ImportBundle/Import/PocketImport.php
Normal file
267
src/Wallabag/ImportBundle/Import/PocketImport.php
Normal file
|
@ -0,0 +1,267 @@
|
|||
<?php
|
||||
|
||||
namespace Wallabag\ImportBundle\Import;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\NullLogger;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||
use Wallabag\CoreBundle\Entity\Entry;
|
||||
use Wallabag\CoreBundle\Entity\Tag;
|
||||
use Wallabag\CoreBundle\Helper\ContentProxy;
|
||||
|
||||
class PocketImport implements ImportInterface
|
||||
{
|
||||
private $user;
|
||||
private $em;
|
||||
private $contentProxy;
|
||||
private $logger;
|
||||
private $client;
|
||||
private $consumerKey;
|
||||
private $skippedEntries = 0;
|
||||
private $importedEntries = 0;
|
||||
protected $accessToken;
|
||||
private $translator;
|
||||
|
||||
public function __construct(TokenStorageInterface $tokenStorage, EntityManager $em, ContentProxy $contentProxy, $consumerKey)
|
||||
{
|
||||
$this->user = $tokenStorage->getToken()->getUser();
|
||||
$this->em = $em;
|
||||
$this->contentProxy = $contentProxy;
|
||||
$this->consumerKey = $consumerKey;
|
||||
$this->logger = new NullLogger();
|
||||
}
|
||||
|
||||
public function setLogger(LoggerInterface $logger)
|
||||
{
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return 'Pocket';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getUrl()
|
||||
{
|
||||
return 'import_pocket';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDescription()
|
||||
{
|
||||
return 'This importer will import all your Pocket data. Pocket doesn\'t allow us to retrieve content from their service, so the readable content of each article will be re-fetched by wallabag.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the oauth url to authenticate the client.
|
||||
*
|
||||
* @param string $redirectUri Redirect url in case of error
|
||||
*
|
||||
* @return string request_token for callback method
|
||||
*/
|
||||
public function getRequestToken($redirectUri)
|
||||
{
|
||||
$request = $this->client->createRequest('POST', 'https://getpocket.com/v3/oauth/request',
|
||||
[
|
||||
'body' => json_encode([
|
||||
'consumer_key' => $this->consumerKey,
|
||||
'redirect_uri' => $redirectUri,
|
||||
]),
|
||||
]
|
||||
);
|
||||
|
||||
try {
|
||||
$response = $this->client->send($request);
|
||||
} catch (RequestException $e) {
|
||||
$this->logger->error(sprintf('PocketImport: Failed to request token: %s', $e->getMessage()), ['exception' => $e]);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return $response->json()['code'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Usually called by the previous callback to authorize the client.
|
||||
* Then it return a token that can be used for next requests.
|
||||
*
|
||||
* @param string $code request_token from getRequestToken
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize($code)
|
||||
{
|
||||
$request = $this->client->createRequest('POST', 'https://getpocket.com/v3/oauth/authorize',
|
||||
[
|
||||
'body' => json_encode([
|
||||
'consumer_key' => $this->consumerKey,
|
||||
'code' => $code,
|
||||
]),
|
||||
]
|
||||
);
|
||||
|
||||
try {
|
||||
$response = $this->client->send($request);
|
||||
} catch (RequestException $e) {
|
||||
$this->logger->error(sprintf('PocketImport: Failed to authorize client: %s', $e->getMessage()), ['exception' => $e]);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->accessToken = $response->json()['access_token'];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function import()
|
||||
{
|
||||
$request = $this->client->createRequest('POST', 'https://getpocket.com/v3/get',
|
||||
[
|
||||
'body' => json_encode([
|
||||
'consumer_key' => $this->consumerKey,
|
||||
'access_token' => $this->accessToken,
|
||||
'detailType' => 'complete',
|
||||
'state' => 'all',
|
||||
'sort' => 'oldest',
|
||||
]),
|
||||
]
|
||||
);
|
||||
|
||||
try {
|
||||
$response = $this->client->send($request);
|
||||
} catch (RequestException $e) {
|
||||
$this->logger->error(sprintf('PocketImport: Failed to import: %s', $e->getMessage()), ['exception' => $e]);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$entries = $response->json();
|
||||
|
||||
$this->parseEntries($entries['list']);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSummary()
|
||||
{
|
||||
return [
|
||||
'skipped' => $this->skippedEntries,
|
||||
'imported' => $this->importedEntries,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Guzzle client.
|
||||
*
|
||||
* @param Client $client
|
||||
*/
|
||||
public function setClient(Client $client)
|
||||
{
|
||||
$this->client = $client;
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo move that in a more global place
|
||||
*/
|
||||
private function assignTagsToEntry(Entry $entry, $tags)
|
||||
{
|
||||
foreach ($tags as $tag) {
|
||||
$label = trim($tag['tag']);
|
||||
$tagEntity = $this->em
|
||||
->getRepository('WallabagCoreBundle:Tag')
|
||||
->findOneByLabel($label);
|
||||
|
||||
if (is_object($tagEntity)) {
|
||||
$entry->addTag($tagEntity);
|
||||
} else {
|
||||
$newTag = new Tag();
|
||||
$newTag->setLabel($label);
|
||||
|
||||
$entry->addTag($newTag);
|
||||
}
|
||||
$this->em->flush();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://getpocket.com/developer/docs/v3/retrieve
|
||||
*
|
||||
* @param $entries
|
||||
*/
|
||||
private function parseEntries($entries)
|
||||
{
|
||||
$i = 1;
|
||||
|
||||
foreach ($entries as $pocketEntry) {
|
||||
$url = isset($pocketEntry['resolved_url']) && $pocketEntry['resolved_url'] != '' ? $pocketEntry['resolved_url'] : $pocketEntry['given_url'];
|
||||
|
||||
$existingEntry = $this->em
|
||||
->getRepository('WallabagCoreBundle:Entry')
|
||||
->existByUrlAndUserId($url, $this->user->getId());
|
||||
|
||||
if (false !== $existingEntry) {
|
||||
++$this->skippedEntries;
|
||||
continue;
|
||||
}
|
||||
|
||||
$entry = new Entry($this->user);
|
||||
$entry = $this->contentProxy->updateEntry($entry, $url);
|
||||
|
||||
// 0, 1, 2 - 1 if the item is archived - 2 if the item should be deleted
|
||||
if ($pocketEntry['status'] == 1) {
|
||||
$entry->setArchived(true);
|
||||
}
|
||||
|
||||
// 0 or 1 - 1 If the item is favorited
|
||||
if ($pocketEntry['favorite'] == 1) {
|
||||
$entry->setStarred(true);
|
||||
}
|
||||
|
||||
$title = 'Untitled';
|
||||
if (isset($pocketEntry['resolved_title']) && $pocketEntry['resolved_title'] != '') {
|
||||
$title = $pocketEntry['resolved_title'];
|
||||
} elseif (isset($pocketEntry['given_title']) && $pocketEntry['given_title'] != '') {
|
||||
$title = $pocketEntry['given_title'];
|
||||
}
|
||||
|
||||
$entry->setTitle($title);
|
||||
|
||||
// 0, 1, or 2 - 1 if the item has images in it - 2 if the item is an image
|
||||
if (isset($pocketEntry['has_image']) && $pocketEntry['has_image'] > 0 && isset($pocketEntry['images'][1])) {
|
||||
$entry->setPreviewPicture($pocketEntry['images'][1]['src']);
|
||||
}
|
||||
|
||||
if (isset($pocketEntry['tags']) && !empty($pocketEntry['tags'])) {
|
||||
$this->assignTagsToEntry($entry, $pocketEntry['tags']);
|
||||
}
|
||||
|
||||
$this->em->persist($entry);
|
||||
++$this->importedEntries;
|
||||
|
||||
// flush every 20 entries
|
||||
if (($i % 20) === 0) {
|
||||
$this->em->flush();
|
||||
}
|
||||
++$i;
|
||||
}
|
||||
|
||||
$this->em->flush();
|
||||
}
|
||||
}
|
159
src/Wallabag/ImportBundle/Import/WallabagV1Import.php
Normal file
159
src/Wallabag/ImportBundle/Import/WallabagV1Import.php
Normal file
|
@ -0,0 +1,159 @@
|
|||
<?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\Tools\Utils;
|
||||
|
||||
class WallabagV1Import implements ImportInterface
|
||||
{
|
||||
private $user;
|
||||
private $em;
|
||||
private $logger;
|
||||
private $skippedEntries = 0;
|
||||
private $importedEntries = 0;
|
||||
private $filepath;
|
||||
|
||||
public function __construct(EntityManager $em)
|
||||
{
|
||||
$this->em = $em;
|
||||
$this->logger = new NullLogger();
|
||||
}
|
||||
|
||||
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
|
||||
*/
|
||||
public function setUser(User $user)
|
||||
{
|
||||
$this->user = $user;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return 'wallabag v1';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getUrl()
|
||||
{
|
||||
return 'import_wallabag_v1';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDescription()
|
||||
{
|
||||
return 'This importer will import all your wallabag v1 articles. On your config page, click on "JSON export" in the "Export your wallabag data" section. You will have a "wallabag-export-1-xxxx-xx-xx.json" file.';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function import()
|
||||
{
|
||||
if (!$this->user) {
|
||||
$this->logger->error('WallabagV1Import: user is not defined');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!file_exists($this->filepath) || !is_readable($this->filepath)) {
|
||||
$this->logger->error('WallabagV1Import: unable to read file', array('filepath' => $this->filepath));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$data = json_decode(file_get_contents($this->filepath), true);
|
||||
|
||||
if (empty($data)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->parseEntries($data);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSummary()
|
||||
{
|
||||
return [
|
||||
'skipped' => $this->skippedEntries,
|
||||
'imported' => $this->importedEntries,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set file path to the json file.
|
||||
*
|
||||
* @param string $filepath
|
||||
*/
|
||||
public function setFilepath($filepath)
|
||||
{
|
||||
$this->filepath = $filepath;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $entries
|
||||
*/
|
||||
private function parseEntries($entries)
|
||||
{
|
||||
$i = 1;
|
||||
|
||||
foreach ($entries as $importedEntry) {
|
||||
$existingEntry = $this->em
|
||||
->getRepository('WallabagCoreBundle:Entry')
|
||||
->existByUrlAndUserId($importedEntry['url'], $this->user->getId());
|
||||
|
||||
if (false !== $existingEntry) {
|
||||
++$this->skippedEntries;
|
||||
continue;
|
||||
}
|
||||
|
||||
// @see ContentProxy->updateEntry
|
||||
$entry = new Entry($this->user);
|
||||
$entry->setUrl($importedEntry['url']);
|
||||
$entry->setTitle($importedEntry['title']);
|
||||
$entry->setArchived($importedEntry['is_read']);
|
||||
$entry->setStarred($importedEntry['is_fav']);
|
||||
$entry->setContent($importedEntry['content']);
|
||||
$entry->setReadingTime(Utils::getReadingTime($importedEntry['content']));
|
||||
$entry->setDomainName(parse_url($importedEntry['url'], PHP_URL_HOST));
|
||||
|
||||
$this->em->persist($entry);
|
||||
++$this->importedEntries;
|
||||
|
||||
// flush every 20 entries
|
||||
if (($i % 20) === 0) {
|
||||
$this->em->flush();
|
||||
}
|
||||
++$i;
|
||||
}
|
||||
|
||||
$this->em->flush();
|
||||
}
|
||||
}
|
34
src/Wallabag/ImportBundle/Resources/config/services.yml
Normal file
34
src/Wallabag/ImportBundle/Resources/config/services.yml
Normal file
|
@ -0,0 +1,34 @@
|
|||
services:
|
||||
wallabag_import.chain:
|
||||
class: Wallabag\ImportBundle\Import\ImportChain
|
||||
|
||||
wallabag_import.pocket.client:
|
||||
class: GuzzleHttp\Client
|
||||
arguments:
|
||||
-
|
||||
defaults:
|
||||
headers:
|
||||
content-type: "application/json"
|
||||
X-Accept: "application/json"
|
||||
|
||||
wallabag_import.pocket.import:
|
||||
class: Wallabag\ImportBundle\Import\PocketImport
|
||||
arguments:
|
||||
- "@security.token_storage"
|
||||
- "@doctrine.orm.entity_manager"
|
||||
- "@wallabag_core.content_proxy"
|
||||
- %pocket_consumer_key%
|
||||
calls:
|
||||
- [ setClient, [ "@wallabag_import.pocket.client" ] ]
|
||||
- [ setLogger, [ "@logger" ]]
|
||||
tags:
|
||||
- { name: wallabag_import.import, alias: pocket }
|
||||
|
||||
wallabag_import.wallabag_v1.import:
|
||||
class: Wallabag\ImportBundle\Import\WallabagV1Import
|
||||
arguments:
|
||||
- "@doctrine.orm.entity_manager"
|
||||
calls:
|
||||
- [ setLogger, [ "@logger" ]]
|
||||
tags:
|
||||
- { name: wallabag_import.import, alias: wallabag_v1 }
|
|
@ -0,0 +1,21 @@
|
|||
{% extends "WallabagCoreBundle::layout.html.twig" %}
|
||||
{% block title %}{% trans %}Import{% endtrans %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col s12">
|
||||
<div class="card-panel settings">
|
||||
{% trans %}Welcome on wallabag importer. Please select your previous service that you want to migrate.{% endtrans %}
|
||||
<ul>
|
||||
{% for import in imports %}
|
||||
<li>
|
||||
<h5>{{ import.name }}</h5>
|
||||
<blockquote>{{ import.description|trans }}</blockquote>
|
||||
<p><a class="waves-effect waves-light btn" href="{{ path(import.url) }}">{% trans %}Import contents{% endtrans %}</a></p>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -0,0 +1,18 @@
|
|||
{% extends "WallabagCoreBundle::layout.html.twig" %}
|
||||
{% block title %}{% trans %}Import > Pocket{% endtrans %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col s12">
|
||||
<div class="card-panel settings">
|
||||
<blockquote>{{ import.description|trans }}</blockquote>
|
||||
<p>{% trans %}You can import your data from your Pocket account. You just have to click on the below button and authorize the application to connect to getpocket.com.{% endtrans %}</p>
|
||||
<form method="post" action="{{ path('import_pocket_auth') }}">
|
||||
<button class="btn waves-effect waves-light" type="submit" name="action">
|
||||
{% trans %}Connect to Pocket and import data{% endtrans %}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -0,0 +1,36 @@
|
|||
{% extends "WallabagCoreBundle::layout.html.twig" %}
|
||||
{% block title %}{% trans %}Import > Wallabag v1{% endtrans %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col s12">
|
||||
<div class="card-panel settings">
|
||||
<div class="row">
|
||||
<blockquote>{{ import.description|trans }}</blockquote>
|
||||
<p>{% trans %}Please select your wallabag export and click on the below button to upload and import it.{% endtrans %}</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>{% trans %}File{% endtrans %}</span>
|
||||
{{ form_widget(form.file) }}
|
||||
</div>
|
||||
<div class="file-path-wrapper">
|
||||
<input class="file-path validate" type="text">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hidden">{{ form_rest(form) }}</div>
|
||||
<button class="btn waves-effect waves-light" type="submit" name="action">
|
||||
{% trans %}Upload file{% endtrans %}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace Wallabag\ImportBundle\Tests\Controller;
|
||||
|
||||
use Wallabag\CoreBundle\Tests\WallabagCoreTestCase;
|
||||
|
||||
class ImportControllerTest extends WallabagCoreTestCase
|
||||
{
|
||||
public function testLogin()
|
||||
{
|
||||
$client = $this->getClient();
|
||||
|
||||
$client->request('GET', '/import/');
|
||||
|
||||
$this->assertEquals(302, $client->getResponse()->getStatusCode());
|
||||
$this->assertContains('login', $client->getResponse()->headers->get('location'));
|
||||
}
|
||||
|
||||
public function testImportList()
|
||||
{
|
||||
$this->logInAs('admin');
|
||||
$client = $this->getClient();
|
||||
|
||||
$crawler = $client->request('GET', '/import/');
|
||||
|
||||
$this->assertEquals(200, $client->getResponse()->getStatusCode());
|
||||
$this->assertEquals(2, $crawler->filter('blockquote')->count());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
namespace Wallabag\ImportBundle\Tests\Controller;
|
||||
|
||||
use Wallabag\CoreBundle\Tests\WallabagCoreTestCase;
|
||||
|
||||
class PocketControllerTest extends WallabagCoreTestCase
|
||||
{
|
||||
public function testImportPocket()
|
||||
{
|
||||
$this->logInAs('admin');
|
||||
$client = $this->getClient();
|
||||
|
||||
$crawler = $client->request('GET', '/import/pocket');
|
||||
|
||||
$this->assertEquals(200, $client->getResponse()->getStatusCode());
|
||||
$this->assertEquals(1, $crawler->filter('button[type=submit]')->count());
|
||||
}
|
||||
|
||||
public function testImportPocketAuth()
|
||||
{
|
||||
$this->logInAs('admin');
|
||||
$client = $this->getClient();
|
||||
|
||||
$crawler = $client->request('GET', '/import/pocket/auth');
|
||||
|
||||
$this->assertEquals(301, $client->getResponse()->getStatusCode());
|
||||
$this->assertContains('getpocket.com/auth/authorize', $client->getResponse()->headers->get('location'));
|
||||
}
|
||||
|
||||
public function testImportPocketCallbackWithBadToken()
|
||||
{
|
||||
$this->logInAs('admin');
|
||||
$client = $this->getClient();
|
||||
|
||||
$crawler = $client->request('GET', '/import/pocket/callback');
|
||||
|
||||
$this->assertEquals(302, $client->getResponse()->getStatusCode());
|
||||
$this->assertContains('import/pocket', $client->getResponse()->headers->get('location'));
|
||||
$this->assertEquals('Import failed, please try again.', $client->getContainer()->get('session')->getFlashBag()->peek('notice')[0]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
|
||||
namespace Wallabag\ImportBundle\Tests\Controller;
|
||||
|
||||
use Wallabag\CoreBundle\Tests\WallabagCoreTestCase;
|
||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||
|
||||
class WallabagV1ControllerTest extends WallabagCoreTestCase
|
||||
{
|
||||
public function testImportWallabag()
|
||||
{
|
||||
$this->logInAs('admin');
|
||||
$client = $this->getClient();
|
||||
|
||||
$crawler = $client->request('GET', '/import/wallabag-v1');
|
||||
|
||||
$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/wallabag-v1');
|
||||
$form = $crawler->filter('form[name=upload_import_file] > button[type=submit]')->form();
|
||||
|
||||
$file = new UploadedFile(__DIR__.'/../fixtures/wallabag-v1.json', 'wallabag-v1.json');
|
||||
|
||||
$data = array(
|
||||
'upload_import_file[file]' => $file,
|
||||
);
|
||||
|
||||
$client->submit($form, $data);
|
||||
|
||||
$this->assertEquals(302, $client->getResponse()->getStatusCode());
|
||||
|
||||
$crawler = $client->followRedirect();
|
||||
|
||||
$this->assertGreaterThan(1, $alert = $crawler->filter('div.messages.success')->extract(array('_text')));
|
||||
$this->assertContains('Import summary', $alert[0]);
|
||||
}
|
||||
|
||||
public function testImportWallabagWithEmptyFile()
|
||||
{
|
||||
$this->logInAs('admin');
|
||||
$client = $this->getClient();
|
||||
|
||||
$crawler = $client->request('GET', '/import/wallabag-v1');
|
||||
$form = $crawler->filter('form[name=upload_import_file] > button[type=submit]')->form();
|
||||
|
||||
$file = new UploadedFile(__DIR__.'/../fixtures/test.txt', 'test.txt');
|
||||
|
||||
$data = array(
|
||||
'upload_import_file[file]' => $file,
|
||||
);
|
||||
|
||||
$client->submit($form, $data);
|
||||
|
||||
$this->assertEquals(302, $client->getResponse()->getStatusCode());
|
||||
|
||||
$crawler = $client->followRedirect();
|
||||
|
||||
$this->assertGreaterThan(1, $alert = $crawler->filter('div.messages.success')->extract(array('_text')));
|
||||
$this->assertContains('Import failed, please try again', $alert[0]);
|
||||
}
|
||||
}
|
21
src/Wallabag/ImportBundle/Tests/Import/ImportChainTest.php
Normal file
21
src/Wallabag/ImportBundle/Tests/Import/ImportChainTest.php
Normal file
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
namespace Wallabag\ImportBundle\Tests\Import;
|
||||
|
||||
use Wallabag\ImportBundle\Import\ImportChain;
|
||||
|
||||
class ImportChainTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testGetAll()
|
||||
{
|
||||
$import = $this->getMockBuilder('Wallabag\ImportBundle\Import\ImportInterface')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
||||
$importChain = new ImportChain();
|
||||
$importChain->addImport($import, 'alias');
|
||||
|
||||
$this->assertCount(1, $importChain->getAll());
|
||||
$this->assertEquals($import, $importChain->getAll()['alias']);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
namespace Wallabag\ImportBundle\Tests\Import;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Wallabag\ImportBundle\Import\ImportCompilerPass;
|
||||
|
||||
class ImportCompilerPassTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testProcessNoDefinition()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
$res = $this->process($container);
|
||||
|
||||
$this->assertNull($res);
|
||||
}
|
||||
|
||||
public function testProcess()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
$container
|
||||
->register('wallabag_import.chain')
|
||||
->setPublic(false)
|
||||
;
|
||||
|
||||
$container
|
||||
->register('foo')
|
||||
->addTag('wallabag_import.import', array('alias' => 'pocket'))
|
||||
;
|
||||
|
||||
$this->process($container);
|
||||
|
||||
$this->assertTrue($container->hasDefinition('wallabag_import.chain'));
|
||||
|
||||
$definition = $container->getDefinition('wallabag_import.chain');
|
||||
$this->assertTrue($definition->hasMethodCall('addImport'));
|
||||
|
||||
$calls = $definition->getMethodCalls();
|
||||
$this->assertEquals('pocket', $calls[0][1][1]);
|
||||
}
|
||||
|
||||
protected function process(ContainerBuilder $container)
|
||||
{
|
||||
$repeatedPass = new ImportCompilerPass();
|
||||
$repeatedPass->process($container);
|
||||
}
|
||||
}
|
314
src/Wallabag/ImportBundle/Tests/Import/PocketImportTest.php
Normal file
314
src/Wallabag/ImportBundle/Tests/Import/PocketImportTest.php
Normal file
|
@ -0,0 +1,314 @@
|
|||
<?php
|
||||
|
||||
namespace Wallabag\ImportBundle\Tests\Import;
|
||||
|
||||
use Wallabag\UserBundle\Entity\User;
|
||||
use Wallabag\ImportBundle\Import\PocketImport;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Subscriber\Mock;
|
||||
use GuzzleHttp\Message\Response;
|
||||
use GuzzleHttp\Stream\Stream;
|
||||
use Monolog\Logger;
|
||||
use Monolog\Handler\TestHandler;
|
||||
|
||||
class PocketImportMock extends PocketImport
|
||||
{
|
||||
public function getAccessToken()
|
||||
{
|
||||
return $this->accessToken;
|
||||
}
|
||||
}
|
||||
|
||||
class PocketImportTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
protected $token;
|
||||
protected $user;
|
||||
protected $em;
|
||||
protected $contentProxy;
|
||||
protected $logHandler;
|
||||
|
||||
private function getPocketImport($consumerKey = 'ConsumerKey')
|
||||
{
|
||||
$this->user = new User();
|
||||
|
||||
$this->tokenStorage = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
||||
$token = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\TokenInterface')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
||||
$this->contentProxy = $this->getMockBuilder('Wallabag\CoreBundle\Helper\ContentProxy')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
||||
$token->expects($this->once())
|
||||
->method('getUser')
|
||||
->willReturn($this->user);
|
||||
|
||||
$this->tokenStorage->expects($this->once())
|
||||
->method('getToken')
|
||||
->willReturn($token);
|
||||
|
||||
$this->em = $this->getMockBuilder('Doctrine\ORM\EntityManager')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
||||
$pocket = new PocketImportMock(
|
||||
$this->tokenStorage,
|
||||
$this->em,
|
||||
$this->contentProxy,
|
||||
$consumerKey
|
||||
);
|
||||
|
||||
$this->logHandler = new TestHandler();
|
||||
$logger = new Logger('test', array($this->logHandler));
|
||||
$pocket->setLogger($logger);
|
||||
|
||||
return $pocket;
|
||||
}
|
||||
|
||||
public function testInit()
|
||||
{
|
||||
$pocketImport = $this->getPocketImport();
|
||||
|
||||
$this->assertEquals('Pocket', $pocketImport->getName());
|
||||
$this->assertNotEmpty($pocketImport->getUrl());
|
||||
$this->assertContains('This importer will import all your Pocket data.', $pocketImport->getDescription());
|
||||
}
|
||||
|
||||
public function testOAuthRequest()
|
||||
{
|
||||
$client = new Client();
|
||||
|
||||
$mock = new Mock([
|
||||
new Response(200, ['Content-Type' => 'application/json'], Stream::factory(json_encode(['code' => 'wunderbar_code']))),
|
||||
]);
|
||||
|
||||
$client->getEmitter()->attach($mock);
|
||||
|
||||
$pocketImport = $this->getPocketImport();
|
||||
$pocketImport->setClient($client);
|
||||
|
||||
$code = $pocketImport->getRequestToken('http://0.0.0.0/redirect');
|
||||
|
||||
$this->assertEquals('wunderbar_code', $code);
|
||||
}
|
||||
|
||||
public function testOAuthRequestBadResponse()
|
||||
{
|
||||
$client = new Client();
|
||||
|
||||
$mock = new Mock([
|
||||
new Response(403),
|
||||
]);
|
||||
|
||||
$client->getEmitter()->attach($mock);
|
||||
|
||||
$pocketImport = $this->getPocketImport();
|
||||
$pocketImport->setClient($client);
|
||||
|
||||
$code = $pocketImport->getRequestToken('http://0.0.0.0/redirect');
|
||||
|
||||
$this->assertFalse($code);
|
||||
|
||||
$records = $this->logHandler->getRecords();
|
||||
$this->assertContains('PocketImport: Failed to request token', $records[0]['message']);
|
||||
$this->assertEquals('ERROR', $records[0]['level_name']);
|
||||
}
|
||||
|
||||
public function testOAuthAuthorize()
|
||||
{
|
||||
$client = new Client();
|
||||
|
||||
$mock = new Mock([
|
||||
new Response(200, ['Content-Type' => 'application/json'], Stream::factory(json_encode(['access_token' => 'wunderbar_token']))),
|
||||
]);
|
||||
|
||||
$client->getEmitter()->attach($mock);
|
||||
|
||||
$pocketImport = $this->getPocketImport();
|
||||
$pocketImport->setClient($client);
|
||||
|
||||
$res = $pocketImport->authorize('wunderbar_code');
|
||||
|
||||
$this->assertTrue($res);
|
||||
$this->assertEquals('wunderbar_token', $pocketImport->getAccessToken());
|
||||
}
|
||||
|
||||
public function testOAuthAuthorizeBadResponse()
|
||||
{
|
||||
$client = new Client();
|
||||
|
||||
$mock = new Mock([
|
||||
new Response(403),
|
||||
]);
|
||||
|
||||
$client->getEmitter()->attach($mock);
|
||||
|
||||
$pocketImport = $this->getPocketImport();
|
||||
$pocketImport->setClient($client);
|
||||
|
||||
$res = $pocketImport->authorize('wunderbar_code');
|
||||
|
||||
$this->assertFalse($res);
|
||||
|
||||
$records = $this->logHandler->getRecords();
|
||||
$this->assertContains('PocketImport: Failed to authorize client', $records[0]['message']);
|
||||
$this->assertEquals('ERROR', $records[0]['level_name']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Will sample results from https://getpocket.com/developer/docs/v3/retrieve.
|
||||
*/
|
||||
public function testImport()
|
||||
{
|
||||
$client = new Client();
|
||||
|
||||
$mock = new Mock([
|
||||
new Response(200, ['Content-Type' => 'application/json'], Stream::factory(json_encode(['access_token' => 'wunderbar_token']))),
|
||||
new Response(200, ['Content-Type' => 'application/json'], Stream::factory('
|
||||
{
|
||||
"status": 1,
|
||||
"list": {
|
||||
"229279689": {
|
||||
"item_id": "229279689",
|
||||
"resolved_id": "229279689",
|
||||
"given_url": "http://www.grantland.com/blog/the-triangle/post/_/id/38347/ryder-cup-preview",
|
||||
"given_title": "The Massive Ryder Cup Preview - The Triangle Blog - Grantland",
|
||||
"favorite": "1",
|
||||
"status": "1",
|
||||
"resolved_title": "The Massive Ryder Cup Preview",
|
||||
"resolved_url": "http://www.grantland.com/blog/the-triangle/post/_/id/38347/ryder-cup-preview",
|
||||
"excerpt": "The list of things I love about the Ryder Cup is so long that it could fill a (tedious) novel, and golf fans can probably guess most of them.",
|
||||
"is_article": "1",
|
||||
"has_video": "1",
|
||||
"has_image": "1",
|
||||
"word_count": "3197",
|
||||
"images": {
|
||||
"1": {
|
||||
"item_id": "229279689",
|
||||
"image_id": "1",
|
||||
"src": "http://a.espncdn.com/combiner/i?img=/photo/2012/0927/grant_g_ryder_cr_640.jpg&w=640&h=360",
|
||||
"width": "0",
|
||||
"height": "0",
|
||||
"credit": "Jamie Squire/Getty Images",
|
||||
"caption": ""
|
||||
}
|
||||
},
|
||||
"videos": {
|
||||
"1": {
|
||||
"item_id": "229279689",
|
||||
"video_id": "1",
|
||||
"src": "http://www.youtube.com/v/Er34PbFkVGk?version=3&hl=en_US&rel=0",
|
||||
"width": "420",
|
||||
"height": "315",
|
||||
"type": "1",
|
||||
"vid": "Er34PbFkVGk"
|
||||
}
|
||||
},
|
||||
"tags": {
|
||||
"grantland": {
|
||||
"item_id": "1147652870",
|
||||
"tag": "grantland"
|
||||
},
|
||||
"Ryder Cup": {
|
||||
"item_id": "1147652870",
|
||||
"tag": "Ryder Cup"
|
||||
}
|
||||
}
|
||||
},
|
||||
"229279690": {
|
||||
"item_id": "229279689",
|
||||
"resolved_id": "229279689",
|
||||
"given_url": "http://www.grantland.com/blog/the-triangle/post/_/id/38347/ryder-cup-preview",
|
||||
"given_title": "The Massive Ryder Cup Preview - The Triangle Blog - Grantland",
|
||||
"favorite": "1",
|
||||
"status": "1",
|
||||
"resolved_title": "The Massive Ryder Cup Preview",
|
||||
"resolved_url": "http://www.grantland.com/blog/the-triangle/post/_/id/38347/ryder-cup-preview",
|
||||
"excerpt": "The list of things I love about the Ryder Cup is so long that it could fill a (tedious) novel, and golf fans can probably guess most of them.",
|
||||
"is_article": "1",
|
||||
"has_video": "0",
|
||||
"has_image": "0",
|
||||
"word_count": "3197"
|
||||
}
|
||||
}
|
||||
}
|
||||
')),
|
||||
]);
|
||||
|
||||
$client->getEmitter()->attach($mock);
|
||||
|
||||
$pocketImport = $this->getPocketImport();
|
||||
|
||||
$entryRepo = $this->getMockBuilder('Wallabag\CoreBundle\Repository\EntryRepository')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
||||
$entryRepo->expects($this->exactly(2))
|
||||
->method('existByUrlAndUserId')
|
||||
->will($this->onConsecutiveCalls(false, true));
|
||||
|
||||
$tag = $this->getMockBuilder('Wallabag\CoreBundle\Entity\Tag')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
||||
$tagRepo = $this->getMockBuilder('Wallabag\CoreBundle\Repository\TagRepository')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
||||
$tagRepo->expects($this->exactly(2))
|
||||
// the method `findOneByLabel` doesn't exist, EntityRepository will then call `_call` method
|
||||
// to magically call the `findOneBy` with ['label' => 'foo']
|
||||
->method('__call')
|
||||
->will($this->onConsecutiveCalls(false, $tag));
|
||||
|
||||
$this->em
|
||||
->expects($this->any())
|
||||
->method('getRepository')
|
||||
->will($this->onConsecutiveCalls($entryRepo, $tagRepo, $tagRepo, $entryRepo));
|
||||
|
||||
$entry = $this->getMockBuilder('Wallabag\CoreBundle\Entity\Entry')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
||||
$this->contentProxy
|
||||
->expects($this->once())
|
||||
->method('updateEntry')
|
||||
->willReturn($entry);
|
||||
|
||||
$pocketImport->setClient($client);
|
||||
$pocketImport->authorize('wunderbar_code');
|
||||
|
||||
$res = $pocketImport->import();
|
||||
|
||||
$this->assertTrue($res);
|
||||
$this->assertEquals(['skipped' => 1, 'imported' => 1], $pocketImport->getSummary());
|
||||
}
|
||||
|
||||
public function testImportBadResponse()
|
||||
{
|
||||
$client = new Client();
|
||||
|
||||
$mock = new Mock([
|
||||
new Response(200, ['Content-Type' => 'application/json'], Stream::factory(json_encode(['access_token' => 'wunderbar_token']))),
|
||||
new Response(403),
|
||||
]);
|
||||
|
||||
$client->getEmitter()->attach($mock);
|
||||
|
||||
$pocketImport = $this->getPocketImport();
|
||||
$pocketImport->setClient($client);
|
||||
$pocketImport->authorize('wunderbar_code');
|
||||
|
||||
$res = $pocketImport->import();
|
||||
|
||||
$this->assertFalse($res);
|
||||
|
||||
$records = $this->logHandler->getRecords();
|
||||
$this->assertContains('PocketImport: Failed to import', $records[0]['message']);
|
||||
$this->assertEquals('ERROR', $records[0]['level_name']);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
<?php
|
||||
|
||||
namespace Wallabag\ImportBundle\Tests\Import;
|
||||
|
||||
use Wallabag\UserBundle\Entity\User;
|
||||
use Wallabag\ImportBundle\Import\WallabagV1Import;
|
||||
use Monolog\Logger;
|
||||
use Monolog\Handler\TestHandler;
|
||||
|
||||
class WallabagV1ImportTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
protected $user;
|
||||
protected $em;
|
||||
protected $logHandler;
|
||||
|
||||
private function getWallabagV1Import($unsetUser = false)
|
||||
{
|
||||
$this->user = new User();
|
||||
|
||||
$this->em = $this->getMockBuilder('Doctrine\ORM\EntityManager')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
||||
$pocket = new WallabagV1Import($this->em);
|
||||
|
||||
$this->logHandler = new TestHandler();
|
||||
$logger = new Logger('test', array($this->logHandler));
|
||||
$pocket->setLogger($logger);
|
||||
|
||||
if (false === $unsetUser) {
|
||||
$pocket->setUser($this->user);
|
||||
}
|
||||
|
||||
return $pocket;
|
||||
}
|
||||
|
||||
public function testInit()
|
||||
{
|
||||
$wallabagV1Import = $this->getWallabagV1Import();
|
||||
|
||||
$this->assertEquals('wallabag v1', $wallabagV1Import->getName());
|
||||
$this->assertNotEmpty($wallabagV1Import->getUrl());
|
||||
$this->assertContains('This importer will import all your wallabag v1 articles.', $wallabagV1Import->getDescription());
|
||||
}
|
||||
|
||||
public function testImport()
|
||||
{
|
||||
$wallabagV1Import = $this->getWallabagV1Import();
|
||||
$wallabagV1Import->setFilepath(__DIR__.'/../fixtures/wallabag-v1.json');
|
||||
|
||||
$entryRepo = $this->getMockBuilder('Wallabag\CoreBundle\Repository\EntryRepository')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
||||
$entryRepo->expects($this->exactly(3))
|
||||
->method('existByUrlAndUserId')
|
||||
->will($this->onConsecutiveCalls(false, true, false));
|
||||
|
||||
$this->em
|
||||
->expects($this->any())
|
||||
->method('getRepository')
|
||||
->willReturn($entryRepo);
|
||||
|
||||
$res = $wallabagV1Import->import();
|
||||
|
||||
$this->assertTrue($res);
|
||||
$this->assertEquals(['skipped' => 1, 'imported' => 2], $wallabagV1Import->getSummary());
|
||||
}
|
||||
|
||||
public function testImportBadFile()
|
||||
{
|
||||
$wallabagV1Import = $this->getWallabagV1Import();
|
||||
$wallabagV1Import->setFilepath(__DIR__.'/../fixtures/wallabag-v1.jsonx');
|
||||
|
||||
$res = $wallabagV1Import->import();
|
||||
|
||||
$this->assertFalse($res);
|
||||
|
||||
$records = $this->logHandler->getRecords();
|
||||
$this->assertContains('WallabagV1Import: unable to read file', $records[0]['message']);
|
||||
$this->assertEquals('ERROR', $records[0]['level_name']);
|
||||
}
|
||||
|
||||
public function testImportUserNotDefined()
|
||||
{
|
||||
$wallabagV1Import = $this->getWallabagV1Import(true);
|
||||
$wallabagV1Import->setFilepath(__DIR__.'/../fixtures/wallabag-v1.json');
|
||||
|
||||
$res = $wallabagV1Import->import();
|
||||
|
||||
$this->assertFalse($res);
|
||||
|
||||
$records = $this->logHandler->getRecords();
|
||||
$this->assertContains('WallabagV1Import: user is not defined', $records[0]['message']);
|
||||
$this->assertEquals('ERROR', $records[0]['level_name']);
|
||||
}
|
||||
}
|
0
src/Wallabag/ImportBundle/Tests/fixtures/test.html
vendored
Normal file
0
src/Wallabag/ImportBundle/Tests/fixtures/test.html
vendored
Normal file
0
src/Wallabag/ImportBundle/Tests/fixtures/test.txt
vendored
Normal file
0
src/Wallabag/ImportBundle/Tests/fixtures/test.txt
vendored
Normal file
50
src/Wallabag/ImportBundle/Tests/fixtures/wallabag-v1.json
vendored
Normal file
50
src/Wallabag/ImportBundle/Tests/fixtures/wallabag-v1.json
vendored
Normal file
File diff suppressed because one or more lines are too long
17
src/Wallabag/ImportBundle/WallabagImportBundle.php
Normal file
17
src/Wallabag/ImportBundle/WallabagImportBundle.php
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
namespace Wallabag\ImportBundle;
|
||||
|
||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Wallabag\ImportBundle\Import\ImportCompilerPass;
|
||||
|
||||
class WallabagImportBundle extends Bundle
|
||||
{
|
||||
public function build(ContainerBuilder $container)
|
||||
{
|
||||
parent::build($container);
|
||||
|
||||
$container->addCompilerPass(new ImportCompilerPass());
|
||||
}
|
||||
}
|
0
web/uploads/import/.gitkeep
Normal file
0
web/uploads/import/.gitkeep
Normal file
Loading…
Reference in a new issue