mirror of
https://github.com/wallabag/wallabag.git
synced 2024-11-26 19:11:07 +00:00
Rewrote Pocket Import
For the moment, we won't do a queue system, just a plain synchronous import. We also use ContentProxy to grab content for each article from Pocket. Error from Pocket are now logged using the logger. The ImportInterface need to be simple and not related to oAuth (not all import will use that method).
This commit is contained in:
parent
b4b592a0c0
commit
252ebd6071
9 changed files with 362 additions and 215 deletions
|
@ -26,16 +26,6 @@ class Utils
|
||||||
return str_replace(array('+', '/'), '', $token);
|
return str_replace(array('+', '/'), '', $token);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param $words
|
|
||||||
*
|
|
||||||
* @return float
|
|
||||||
*/
|
|
||||||
public static function convertWordsToMinutes($words)
|
|
||||||
{
|
|
||||||
return floor($words / 200);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For a given text, we calculate reading time for an article
|
* For a given text, we calculate reading time for an article
|
||||||
* based on 200 words per minute.
|
* based on 200 words per minute.
|
||||||
|
@ -46,6 +36,6 @@ class Utils
|
||||||
*/
|
*/
|
||||||
public static function getReadingTime($text)
|
public static function getReadingTime($text)
|
||||||
{
|
{
|
||||||
return self::convertWordsToMinutes(str_word_count(strip_tags($text)));
|
return floor(str_word_count(strip_tags($text)) / 200);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,58 +4,14 @@ namespace Wallabag\ImportBundle\Controller;
|
||||||
|
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
|
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
|
||||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
|
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
|
||||||
use Symfony\Component\Console\Input\ArrayInput;
|
|
||||||
use Symfony\Component\Console\Output\NullOutput;
|
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
|
||||||
use Wallabag\ImportBundle\Command\ImportCommand;
|
|
||||||
use Wallabag\ImportBundle\Form\Type\UploadImportType;
|
|
||||||
|
|
||||||
class ImportController extends Controller
|
class ImportController extends Controller
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @Route("/import", name="import")
|
* @Route("/import", name="import")
|
||||||
*/
|
*/
|
||||||
public function importAction(Request $request)
|
public function importAction()
|
||||||
{
|
{
|
||||||
$importForm = $this->createForm(new UploadImportType());
|
return $this->render('WallabagImportBundle:Import:index.html.twig', []);
|
||||||
$importForm->handleRequest($request);
|
|
||||||
$user = $this->getUser();
|
|
||||||
|
|
||||||
if ($importForm->isValid()) {
|
|
||||||
$file = $importForm->get('file')->getData();
|
|
||||||
$name = $user->getId().'.json';
|
|
||||||
$dir = __DIR__.'/../../../../web/uploads/import';
|
|
||||||
|
|
||||||
if (in_array($file->getMimeType(), $this->getParameter('wallabag_import.allow_mimetypes')) && $file->move($dir, $name)) {
|
|
||||||
$command = new ImportCommand();
|
|
||||||
$command->setContainer($this->container);
|
|
||||||
$input = new ArrayInput(array('userId' => $user->getId()));
|
|
||||||
$return = $command->run($input, new NullOutput());
|
|
||||||
|
|
||||||
if ($return == 0) {
|
|
||||||
$this->get('session')->getFlashBag()->add(
|
|
||||||
'notice',
|
|
||||||
'Import successful'
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
$this->get('session')->getFlashBag()->add(
|
|
||||||
'notice',
|
|
||||||
'Import failed'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->redirect('/');
|
|
||||||
} else {
|
|
||||||
$this->get('session')->getFlashBag()->add(
|
|
||||||
'notice',
|
|
||||||
'Error while processing import. Please verify your import file.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->render('WallabagImportBundle:Import:index.html.twig', array(
|
|
||||||
'form' => array(
|
|
||||||
'import' => $importForm->createView(), ),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,35 +8,56 @@ use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
|
||||||
class PocketController extends Controller
|
class PocketController extends Controller
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @Route("/import/pocket", name="pocket_import")
|
* @Route("/import/pocket", name="import_pocket")
|
||||||
*/
|
*/
|
||||||
public function indexAction()
|
public function indexAction()
|
||||||
{
|
{
|
||||||
return $this->render('WallabagImportBundle:Pocket:index.html.twig', array());
|
return $this->render('WallabagImportBundle:Pocket:index.html.twig', []);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Route("/import/pocket/auth", name="pocket_auth")
|
* @Route("/import/pocket/auth", name="import_pocket_auth")
|
||||||
*/
|
*/
|
||||||
public function authAction()
|
public function authAction()
|
||||||
{
|
{
|
||||||
$pocket = $this->get('wallabag_import.pocket.import');
|
$requestToken = $this->get('wallabag_import.pocket.import')
|
||||||
$authUrl = $pocket->oAuthRequest(
|
->getRequestToken($this->generateUrl('import', [], true));
|
||||||
$this->generateUrl('import', array(), true),
|
|
||||||
$this->generateUrl('pocket_callback', array(), true)
|
|
||||||
);
|
|
||||||
|
|
||||||
return $this->redirect($authUrl, 301);
|
$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("/import/pocket/callback", name="pocket_callback")
|
* @Route("/import/pocket/callback", name="import_pocket_callback")
|
||||||
*/
|
*/
|
||||||
public function callbackAction()
|
public function callbackAction()
|
||||||
{
|
{
|
||||||
|
$message = 'Import failed, please try again.';
|
||||||
$pocket = $this->get('wallabag_import.pocket.import');
|
$pocket = $this->get('wallabag_import.pocket.import');
|
||||||
$accessToken = $pocket->oAuthAuthorize();
|
|
||||||
$pocket->import($accessToken);
|
// 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 = $summary['imported'].' entrie(s) imported, '.$summary['skipped'].' already saved.';
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->get('session')->getFlashBag()->add(
|
||||||
|
'notice',
|
||||||
|
$message
|
||||||
|
);
|
||||||
|
|
||||||
return $this->redirect($this->generateUrl('homepage'));
|
return $this->redirect($this->generateUrl('homepage'));
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,9 @@
|
||||||
|
|
||||||
namespace Wallabag\ImportBundle\Import;
|
namespace Wallabag\ImportBundle\Import;
|
||||||
|
|
||||||
interface ImportInterface
|
use Psr\Log\LoggerAwareInterface;
|
||||||
|
|
||||||
|
interface ImportInterface extends LoggerAwareInterface
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Name of the import.
|
* Name of the import.
|
||||||
|
@ -18,28 +20,19 @@ interface ImportInterface
|
||||||
*/
|
*/
|
||||||
public function getDescription();
|
public function getDescription();
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the oauth url to authenticate the client.
|
|
||||||
*
|
|
||||||
* @param string $redirectUri Redirect url in case of error
|
|
||||||
* @param string $callbackUri Url when the authentication is complete
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function oAuthRequest($redirectUri, $callbackUri);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Usually called by the previous callback to authorize the client.
|
|
||||||
* Then it return a token that can be used for next requests.
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function oAuthAuthorize();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Import content using the user token.
|
* Import content using the user token.
|
||||||
*
|
*
|
||||||
* @param string $accessToken User access token
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function import($accessToken);
|
public function import();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return an array with summary info about the import, with keys:
|
||||||
|
* - skipped
|
||||||
|
* - imported.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getSummary();
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,29 +2,39 @@
|
||||||
|
|
||||||
namespace Wallabag\ImportBundle\Import;
|
namespace Wallabag\ImportBundle\Import;
|
||||||
|
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
use Psr\Log\NullLogger;
|
||||||
use Doctrine\ORM\EntityManager;
|
use Doctrine\ORM\EntityManager;
|
||||||
use GuzzleHttp\Client;
|
use GuzzleHttp\Client;
|
||||||
use Symfony\Component\HttpFoundation\Session\Session;
|
use GuzzleHttp\Exception\RequestException;
|
||||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||||
use Wallabag\CoreBundle\Entity\Entry;
|
use Wallabag\CoreBundle\Entity\Entry;
|
||||||
use Wallabag\CoreBundle\Entity\Tag;
|
use Wallabag\CoreBundle\Entity\Tag;
|
||||||
use Wallabag\CoreBundle\Tools\Utils;
|
use Wallabag\CoreBundle\Helper\ContentProxy;
|
||||||
|
|
||||||
class PocketImport implements ImportInterface
|
class PocketImport implements ImportInterface
|
||||||
{
|
{
|
||||||
private $user;
|
private $user;
|
||||||
private $session;
|
|
||||||
private $em;
|
private $em;
|
||||||
|
private $contentProxy;
|
||||||
|
private $logger;
|
||||||
private $consumerKey;
|
private $consumerKey;
|
||||||
private $skippedEntries = 0;
|
private $skippedEntries = 0;
|
||||||
private $importedEntries = 0;
|
private $importedEntries = 0;
|
||||||
|
protected $accessToken;
|
||||||
|
|
||||||
public function __construct(TokenStorageInterface $tokenStorage, Session $session, EntityManager $em, $consumerKey)
|
public function __construct(TokenStorageInterface $tokenStorage, EntityManager $em, ContentProxy $contentProxy, $consumerKey)
|
||||||
{
|
{
|
||||||
$this->user = $tokenStorage->getToken()->getUser();
|
$this->user = $tokenStorage->getToken()->getUser();
|
||||||
$this->session = $session;
|
|
||||||
$this->em = $em;
|
$this->em = $em;
|
||||||
|
$this->contentProxy = $contentProxy;
|
||||||
$this->consumerKey = $consumerKey;
|
$this->consumerKey = $consumerKey;
|
||||||
|
$this->logger = new NullLogger();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setLogger(LoggerInterface $logger)
|
||||||
|
{
|
||||||
|
$this->logger = $logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -44,9 +54,13 @@ class PocketImport implements ImportInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* 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 oAuthRequest($redirectUri, $callbackUri)
|
public function getRequestToken($redirectUri)
|
||||||
{
|
{
|
||||||
$request = $this->client->createRequest('POST', 'https://getpocket.com/v3/oauth/request',
|
$request = $this->client->createRequest('POST', 'https://getpocket.com/v3/oauth/request',
|
||||||
[
|
[
|
||||||
|
@ -57,44 +71,59 @@ class PocketImport implements ImportInterface
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
$response = $this->client->send($request);
|
$response = $this->client->send($request);
|
||||||
$values = $response->json();
|
} catch (RequestException $e) {
|
||||||
|
$this->logger->error(sprintf('PocketImport: Failed to request token: %s', $e->getMessage()), ['exception' => $e]);
|
||||||
|
|
||||||
// store code in session for callback method
|
return false;
|
||||||
$this->session->set('pocketCode', $values['code']);
|
}
|
||||||
|
|
||||||
return 'https://getpocket.com/auth/authorize?request_token='.$values['code'].'&redirect_uri='.$callbackUri;
|
return $response->json()['code'];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* 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 oAuthAuthorize()
|
public function authorize($code)
|
||||||
{
|
{
|
||||||
$request = $this->client->createRequest('POST', 'https://getpocket.com/v3/oauth/authorize',
|
$request = $this->client->createRequest('POST', 'https://getpocket.com/v3/oauth/authorize',
|
||||||
[
|
[
|
||||||
'body' => json_encode([
|
'body' => json_encode([
|
||||||
'consumer_key' => $this->consumerKey,
|
'consumer_key' => $this->consumerKey,
|
||||||
'code' => $this->session->get('pocketCode'),
|
'code' => $code,
|
||||||
]),
|
]),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
$response = $this->client->send($request);
|
$response = $this->client->send($request);
|
||||||
|
} catch (RequestException $e) {
|
||||||
|
$this->logger->error(sprintf('PocketImport: Failed to authorize client: %s', $e->getMessage()), ['exception' => $e]);
|
||||||
|
|
||||||
return $response->json()['access_token'];
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->accessToken = $response->json()['access_token'];
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public function import($accessToken)
|
public function import()
|
||||||
{
|
{
|
||||||
$request = $this->client->createRequest('POST', 'https://getpocket.com/v3/get',
|
$request = $this->client->createRequest('POST', 'https://getpocket.com/v3/get',
|
||||||
[
|
[
|
||||||
'body' => json_encode([
|
'body' => json_encode([
|
||||||
'consumer_key' => $this->consumerKey,
|
'consumer_key' => $this->consumerKey,
|
||||||
'access_token' => $accessToken,
|
'access_token' => $this->accessToken,
|
||||||
'detailType' => 'complete',
|
'detailType' => 'complete',
|
||||||
'state' => 'all',
|
'state' => 'all',
|
||||||
'sort' => 'oldest',
|
'sort' => 'oldest',
|
||||||
|
@ -102,15 +131,30 @@ class PocketImport implements ImportInterface
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
$response = $this->client->send($request);
|
$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();
|
$entries = $response->json();
|
||||||
|
|
||||||
$this->parsePocketEntries($entries['list']);
|
$this->parsePocketEntries($entries['list']);
|
||||||
|
|
||||||
$this->session->getFlashBag()->add(
|
return true;
|
||||||
'notice',
|
}
|
||||||
$this->importedEntries.' entries imported, '.$this->skippedEntries.' already saved.'
|
|
||||||
);
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getSummary()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'skipped' => $this->skippedEntries,
|
||||||
|
'imported' => $this->importedEntries,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -124,39 +168,8 @@ class PocketImport implements ImportInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the good title for current entry.
|
* @todo move that in a more global place
|
||||||
*
|
|
||||||
* @param $pocketEntry
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
*/
|
||||||
private function guessTitle($pocketEntry)
|
|
||||||
{
|
|
||||||
if (isset($pocketEntry['resolved_title']) && $pocketEntry['resolved_title'] != '') {
|
|
||||||
return $pocketEntry['resolved_title'];
|
|
||||||
} elseif (isset($pocketEntry['given_title']) && $pocketEntry['given_title'] != '') {
|
|
||||||
return $pocketEntry['given_title'];
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'Untitled';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the good URL for current entry.
|
|
||||||
*
|
|
||||||
* @param $pocketEntry
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
private function guessURL($pocketEntry)
|
|
||||||
{
|
|
||||||
if (isset($pocketEntry['resolved_url']) && $pocketEntry['resolved_url'] != '') {
|
|
||||||
return $pocketEntry['resolved_url'];
|
|
||||||
}
|
|
||||||
|
|
||||||
return $pocketEntry['given_url'];
|
|
||||||
}
|
|
||||||
|
|
||||||
private function assignTagsToEntry(Entry $entry, $tags)
|
private function assignTagsToEntry(Entry $entry, $tags)
|
||||||
{
|
{
|
||||||
foreach ($tags as $tag) {
|
foreach ($tags as $tag) {
|
||||||
|
@ -177,13 +190,16 @@ class PocketImport implements ImportInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @see https://getpocket.com/developer/docs/v3/retrieve
|
||||||
|
*
|
||||||
* @param $entries
|
* @param $entries
|
||||||
*/
|
*/
|
||||||
private function parsePocketEntries($entries)
|
private function parsePocketEntries($entries)
|
||||||
{
|
{
|
||||||
foreach ($entries as $pocketEntry) {
|
foreach ($entries as $pocketEntry) {
|
||||||
$entry = new Entry($this->user);
|
$entry = new Entry($this->user);
|
||||||
$url = $this->guessURL($pocketEntry);
|
|
||||||
|
$url = isset($pocketEntry['resolved_url']) && $pocketEntry['resolved_url'] != '' ? $pocketEntry['resolved_url'] : $pocketEntry['given_url'];
|
||||||
|
|
||||||
$existingEntry = $this->em
|
$existingEntry = $this->em
|
||||||
->getRepository('WallabagCoreBundle:Entry')
|
->getRepository('WallabagCoreBundle:Entry')
|
||||||
|
@ -194,31 +210,33 @@ class PocketImport implements ImportInterface
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$entry->setUrl($url);
|
$entry = $this->contentProxy->updateEntry($entry, $url);
|
||||||
$entry->setDomainName(parse_url($url, PHP_URL_HOST));
|
|
||||||
|
|
||||||
|
// 0, 1, 2 - 1 if the item is archived - 2 if the item should be deleted
|
||||||
if ($pocketEntry['status'] == 1) {
|
if ($pocketEntry['status'] == 1) {
|
||||||
$entry->setArchived(true);
|
$entry->setArchived(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 0 or 1 - 1 If the item is favorited
|
||||||
if ($pocketEntry['favorite'] == 1) {
|
if ($pocketEntry['favorite'] == 1) {
|
||||||
$entry->setStarred(true);
|
$entry->setStarred(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
$entry->setTitle($this->guessTitle($pocketEntry));
|
$title = 'Untitled';
|
||||||
|
if (isset($pocketEntry['resolved_title']) && $pocketEntry['resolved_title'] != '') {
|
||||||
if (isset($pocketEntry['excerpt'])) {
|
$title = $pocketEntry['resolved_title'];
|
||||||
$entry->setContent($pocketEntry['excerpt']);
|
} elseif (isset($pocketEntry['given_title']) && $pocketEntry['given_title'] != '') {
|
||||||
|
$title = $pocketEntry['given_title'];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isset($pocketEntry['has_image']) && $pocketEntry['has_image'] > 0) {
|
$entry->setTitle($title);
|
||||||
$entry->setPreviewPicture($pocketEntry['image']['src']);
|
|
||||||
|
// 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['word_count'])) {
|
if (isset($pocketEntry['tags']) && !empty($pocketEntry['tags'])) {
|
||||||
$entry->setReadingTime(Utils::convertWordsToMinutes($pocketEntry['word_count']));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!empty($pocketEntry['tags'])) {
|
|
||||||
$this->assignTagsToEntry($entry, $pocketEntry['tags']);
|
$this->assignTagsToEntry($entry, $pocketEntry['tags']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,4 @@
|
||||||
services:
|
services:
|
||||||
wallabag_import.pocket.import:
|
|
||||||
class: Wallabag\ImportBundle\Import\PocketImport
|
|
||||||
arguments:
|
|
||||||
- "@security.token_storage"
|
|
||||||
- "@session"
|
|
||||||
- "@doctrine.orm.entity_manager"
|
|
||||||
- %pocket_consumer_key%
|
|
||||||
calls:
|
|
||||||
- [ setClient, [ "@wallabag_import.pocket.client" ] ]
|
|
||||||
|
|
||||||
wallabag_import.pocket.client:
|
wallabag_import.pocket.client:
|
||||||
class: GuzzleHttp\Client
|
class: GuzzleHttp\Client
|
||||||
arguments:
|
arguments:
|
||||||
|
@ -17,3 +7,14 @@ services:
|
||||||
headers:
|
headers:
|
||||||
content-type: "application/json"
|
content-type: "application/json"
|
||||||
X-Accept: "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" ]]
|
||||||
|
|
|
@ -8,35 +8,9 @@
|
||||||
<div class="card-panel settings">
|
<div class="card-panel settings">
|
||||||
{% trans %}Welcome on wallabag importer. Please select your previous service that you want to migrate.{% endtrans %}
|
{% trans %}Welcome on wallabag importer. Please select your previous service that you want to migrate.{% endtrans %}
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="{{ path('pocket_import') }}">Pocket</a></li>
|
<li><a href="{{ path('import_pocket') }}">Pocket</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col s12">
|
|
||||||
<div class="card-panel settings">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col s12">
|
|
||||||
<form action="{{ path('import') }}" method="post" {{ form_enctype(form.import) }}>
|
|
||||||
{{ form_errors(form.import) }}
|
|
||||||
<div class="row">
|
|
||||||
<div class="input-field col s12">
|
|
||||||
<p>{% trans %}Please select your wallabag export and click on the below button to upload and import it.{% endtrans %}</p>
|
|
||||||
{{ form_errors(form.import.file) }}
|
|
||||||
{{ form_widget(form.import.file) }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="hidden">{{ form_rest(form.import) }}</div>
|
|
||||||
<button class="btn waves-effect waves-light" type="submit" name="action">
|
|
||||||
{% trans %}Upload file{% endtrans %}
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
<div class="col s12">
|
<div class="col s12">
|
||||||
<div class="card-panel settings">
|
<div class="card-panel settings">
|
||||||
{% 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 %}
|
{% 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 %}
|
||||||
<form method="post" action="{{ path('pocket_auth') }}">
|
<form method="post" action="{{ path('import_pocket_auth') }}">
|
||||||
<input type="submit" value="Connect to Pocket and import data" />
|
<input type="submit" value="Connect to Pocket and import data" />
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -4,19 +4,28 @@ namespace Wallabag\ImportBundle\Tests\Import;
|
||||||
|
|
||||||
use Wallabag\UserBundle\Entity\User;
|
use Wallabag\UserBundle\Entity\User;
|
||||||
use Wallabag\ImportBundle\Import\PocketImport;
|
use Wallabag\ImportBundle\Import\PocketImport;
|
||||||
use Symfony\Component\HttpFoundation\Session\Session;
|
|
||||||
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
|
|
||||||
use GuzzleHttp\Client;
|
use GuzzleHttp\Client;
|
||||||
use GuzzleHttp\Subscriber\Mock;
|
use GuzzleHttp\Subscriber\Mock;
|
||||||
use GuzzleHttp\Message\Response;
|
use GuzzleHttp\Message\Response;
|
||||||
use GuzzleHttp\Stream\Stream;
|
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
|
class PocketImportTest extends \PHPUnit_Framework_TestCase
|
||||||
{
|
{
|
||||||
protected $token;
|
protected $token;
|
||||||
protected $user;
|
protected $user;
|
||||||
protected $session;
|
|
||||||
protected $em;
|
protected $em;
|
||||||
|
protected $contentProxy;
|
||||||
|
protected $logHandler;
|
||||||
|
|
||||||
private function getPocketImport($consumerKey = 'ConsumerKey')
|
private function getPocketImport($consumerKey = 'ConsumerKey')
|
||||||
{
|
{
|
||||||
|
@ -30,6 +39,10 @@ class PocketImportTest extends \PHPUnit_Framework_TestCase
|
||||||
->disableOriginalConstructor()
|
->disableOriginalConstructor()
|
||||||
->getMock();
|
->getMock();
|
||||||
|
|
||||||
|
$this->contentProxy = $this->getMockBuilder('Wallabag\CoreBundle\Helper\ContentProxy')
|
||||||
|
->disableOriginalConstructor()
|
||||||
|
->getMock();
|
||||||
|
|
||||||
$token->expects($this->once())
|
$token->expects($this->once())
|
||||||
->method('getUser')
|
->method('getUser')
|
||||||
->willReturn($this->user);
|
->willReturn($this->user);
|
||||||
|
@ -38,18 +51,22 @@ class PocketImportTest extends \PHPUnit_Framework_TestCase
|
||||||
->method('getToken')
|
->method('getToken')
|
||||||
->willReturn($token);
|
->willReturn($token);
|
||||||
|
|
||||||
$this->session = new Session(new MockArraySessionStorage());
|
|
||||||
|
|
||||||
$this->em = $this->getMockBuilder('Doctrine\ORM\EntityManager')
|
$this->em = $this->getMockBuilder('Doctrine\ORM\EntityManager')
|
||||||
->disableOriginalConstructor()
|
->disableOriginalConstructor()
|
||||||
->getMock();
|
->getMock();
|
||||||
|
|
||||||
return new PocketImport(
|
$pocket = new PocketImportMock(
|
||||||
$this->tokenStorage,
|
$this->tokenStorage,
|
||||||
$this->session,
|
|
||||||
$this->em,
|
$this->em,
|
||||||
|
$this->contentProxy,
|
||||||
$consumerKey
|
$consumerKey
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$this->logHandler = new TestHandler();
|
||||||
|
$logger = new Logger('test', array($this->logHandler));
|
||||||
|
$pocket->setLogger($logger);
|
||||||
|
|
||||||
|
return $pocket;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testInit()
|
public function testInit()
|
||||||
|
@ -65,7 +82,7 @@ class PocketImportTest extends \PHPUnit_Framework_TestCase
|
||||||
$client = new Client();
|
$client = new Client();
|
||||||
|
|
||||||
$mock = new Mock([
|
$mock = new Mock([
|
||||||
new Response(200, ['Content-Type' => 'application/json'], Stream::factory(json_encode(['code' => 'wunderbar']))),
|
new Response(200, ['Content-Type' => 'application/json'], Stream::factory(json_encode(['code' => 'wunderbar_code']))),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$client->getEmitter()->attach($mock);
|
$client->getEmitter()->attach($mock);
|
||||||
|
@ -73,10 +90,31 @@ class PocketImportTest extends \PHPUnit_Framework_TestCase
|
||||||
$pocketImport = $this->getPocketImport();
|
$pocketImport = $this->getPocketImport();
|
||||||
$pocketImport->setClient($client);
|
$pocketImport->setClient($client);
|
||||||
|
|
||||||
$url = $pocketImport->oAuthRequest('http://0.0.0.0./redirect', 'http://0.0.0.0./callback');
|
$code = $pocketImport->getRequestToken('http://0.0.0.0/redirect');
|
||||||
|
|
||||||
$this->assertEquals('https://getpocket.com/auth/authorize?request_token=wunderbar&redirect_uri=http://0.0.0.0./callback', $url);
|
$this->assertEquals('wunderbar_code', $code);
|
||||||
$this->assertEquals('wunderbar', $this->session->get('pocketCode'));
|
}
|
||||||
|
|
||||||
|
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()
|
public function testOAuthAuthorize()
|
||||||
|
@ -84,7 +122,7 @@ class PocketImportTest extends \PHPUnit_Framework_TestCase
|
||||||
$client = new Client();
|
$client = new Client();
|
||||||
|
|
||||||
$mock = new Mock([
|
$mock = new Mock([
|
||||||
new Response(200, ['Content-Type' => 'application/json'], Stream::factory(json_encode(['access_token' => 'wunderbar']))),
|
new Response(200, ['Content-Type' => 'application/json'], Stream::factory(json_encode(['access_token' => 'wunderbar_token']))),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$client->getEmitter()->attach($mock);
|
$client->getEmitter()->attach($mock);
|
||||||
|
@ -92,26 +130,182 @@ class PocketImportTest extends \PHPUnit_Framework_TestCase
|
||||||
$pocketImport = $this->getPocketImport();
|
$pocketImport = $this->getPocketImport();
|
||||||
$pocketImport->setClient($client);
|
$pocketImport->setClient($client);
|
||||||
|
|
||||||
$accessToken = $pocketImport->oAuthAuthorize();
|
$res = $pocketImport->authorize('wunderbar_code');
|
||||||
|
|
||||||
$this->assertEquals('wunderbar', $accessToken);
|
$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()
|
public function testImport()
|
||||||
{
|
{
|
||||||
$client = new Client();
|
$client = new Client();
|
||||||
|
|
||||||
$mock = new Mock([
|
$mock = new Mock([
|
||||||
new Response(200, ['Content-Type' => 'application/json'], Stream::factory(json_encode(['list' => []]))),
|
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))
|
||||||
|
->method('findOneByLabelAndUserId')
|
||||||
|
->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);
|
$client->getEmitter()->attach($mock);
|
||||||
|
|
||||||
$pocketImport = $this->getPocketImport();
|
$pocketImport = $this->getPocketImport();
|
||||||
$pocketImport->setClient($client);
|
$pocketImport->setClient($client);
|
||||||
|
$pocketImport->authorize('wunderbar_code');
|
||||||
|
|
||||||
$pocketImport->import('wunderbar');
|
$res = $pocketImport->import();
|
||||||
|
|
||||||
$this->assertEquals('0 entries imported, 0 already saved.', $this->session->getFlashBag()->get('notice')[0]);
|
$this->assertFalse($res);
|
||||||
|
|
||||||
|
$records = $this->logHandler->getRecords();
|
||||||
|
$this->assertContains('PocketImport: Failed to import', $records[0]['message']);
|
||||||
|
$this->assertEquals('ERROR', $records[0]['level_name']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue