]> git.immae.eu Git - github/wallabag/wallabag.git/blame - src/Wallabag/ImportBundle/Import/PocketImport.php
Send every imported item to the queue
[github/wallabag/wallabag.git] / src / Wallabag / ImportBundle / Import / PocketImport.php
CommitLineData
ff7b031d
NL
1<?php
2
3namespace Wallabag\ImportBundle\Import;
4
56c778b4 5use OldSound\RabbitMqBundle\RabbitMq\Producer;
252ebd60 6use Psr\Log\NullLogger;
ff7b031d
NL
7use Doctrine\ORM\EntityManager;
8use GuzzleHttp\Client;
252ebd60 9use GuzzleHttp\Exception\RequestException;
ef75e122 10use Symfony\Component\Security\Core\User\UserInterface;
ff7b031d 11use Wallabag\CoreBundle\Entity\Entry;
252ebd60 12use Wallabag\CoreBundle\Helper\ContentProxy;
63e40f2d 13use Craue\ConfigBundle\Util\Config;
ff7b031d 14
19d9efab 15class PocketImport extends AbstractImport
ff7b031d
NL
16{
17 private $user;
8eedc8cf 18 private $client;
ff7b031d 19 private $consumerKey;
303768df
NL
20 private $skippedEntries = 0;
21 private $importedEntries = 0;
c10fcb3b 22 private $markAsRead;
56c778b4 23 private $producer;
ef75e122 24 protected $accessToken;
ff7b031d 25
ef75e122 26 public function __construct(EntityManager $em, ContentProxy $contentProxy, Config $craueConfig)
ff7b031d 27 {
ff7b031d 28 $this->em = $em;
252ebd60 29 $this->contentProxy = $contentProxy;
63e40f2d 30 $this->consumerKey = $craueConfig->get('pocket_consumer_key');
252ebd60 31 $this->logger = new NullLogger();
ef75e122
JB
32 }
33
34 /**
35 * Set RabbitMQ Producer to send each entry to a queue.
36 * This method should be called when user has enabled RabbitMQ.
37 *
38 * @param Producer $producer
39 */
40 public function setRabbitmqProducer(Producer $producer)
41 {
56c778b4 42 $this->producer = $producer;
252ebd60
JB
43 }
44
ef75e122
JB
45 /**
46 * Set current user.
47 * Could the current *connected* user or one retrieve by the consumer.
48 *
49 * @param UserInterface $user
50 */
51 public function setUser(UserInterface $user)
52 {
53 $this->user = $user;
54 }
55
0aa344dc
JB
56 /**
57 * {@inheritdoc}
58 */
d51b38ed
NL
59 public function getName()
60 {
61 return 'Pocket';
62 }
63
7019c7cf
JB
64 /**
65 * {@inheritdoc}
66 */
67 public function getUrl()
68 {
69 return 'import_pocket';
70 }
71
0aa344dc
JB
72 /**
73 * {@inheritdoc}
74 */
d51b38ed
NL
75 public function getDescription()
76 {
0d42217e 77 return 'import.pocket.description';
d51b38ed
NL
78 }
79
ff7b031d 80 /**
252ebd60
JB
81 * Return the oauth url to authenticate the client.
82 *
83 * @param string $redirectUri Redirect url in case of error
84 *
4d0ec0e7 85 * @return string|false request_token for callback method
7ec2897e 86 */
252ebd60 87 public function getRequestToken($redirectUri)
7ec2897e
JB
88 {
89 $request = $this->client->createRequest('POST', 'https://getpocket.com/v3/oauth/request',
90 [
91 'body' => json_encode([
92 'consumer_key' => $this->consumerKey,
93 'redirect_uri' => $redirectUri,
94 ]),
95 ]
96 );
97
252ebd60
JB
98 try {
99 $response = $this->client->send($request);
100 } catch (RequestException $e) {
101 $this->logger->error(sprintf('PocketImport: Failed to request token: %s', $e->getMessage()), ['exception' => $e]);
7ec2897e 102
252ebd60
JB
103 return false;
104 }
7ec2897e 105
252ebd60 106 return $response->json()['code'];
7ec2897e
JB
107 }
108
109 /**
252ebd60
JB
110 * Usually called by the previous callback to authorize the client.
111 * Then it return a token that can be used for next requests.
112 *
113 * @param string $code request_token from getRequestToken
114 *
115 * @return bool
7ec2897e 116 */
252ebd60 117 public function authorize($code)
7ec2897e
JB
118 {
119 $request = $this->client->createRequest('POST', 'https://getpocket.com/v3/oauth/authorize',
120 [
121 'body' => json_encode([
122 'consumer_key' => $this->consumerKey,
252ebd60 123 'code' => $code,
7ec2897e
JB
124 ]),
125 ]
126 );
127
252ebd60
JB
128 try {
129 $response = $this->client->send($request);
130 } catch (RequestException $e) {
131 $this->logger->error(sprintf('PocketImport: Failed to authorize client: %s', $e->getMessage()), ['exception' => $e]);
7ec2897e 132
252ebd60
JB
133 return false;
134 }
135
136 $this->accessToken = $response->json()['access_token'];
137
138 return true;
7ec2897e
JB
139 }
140
c10fcb3b
TC
141 /**
142 * Set whether articles must be all marked as read.
143 *
144 * @param bool $markAsRead
145 */
146 public function setMarkAsRead($markAsRead)
147 {
148 $this->markAsRead = $markAsRead;
149
150 return $this;
151 }
152
153 /**
154 * Get whether articles must be all marked as read.
155 */
0d42217e 156 public function getMarkAsRead()
c10fcb3b
TC
157 {
158 return $this->markAsRead;
159 }
160
7ec2897e
JB
161 /**
162 * {@inheritdoc}
163 */
252ebd60 164 public function import()
7ec2897e
JB
165 {
166 $request = $this->client->createRequest('POST', 'https://getpocket.com/v3/get',
167 [
168 'body' => json_encode([
169 'consumer_key' => $this->consumerKey,
252ebd60 170 'access_token' => $this->accessToken,
7ec2897e
JB
171 'detailType' => 'complete',
172 'state' => 'all',
173 'sort' => 'oldest',
174 ]),
175 ]
176 );
177
252ebd60
JB
178 try {
179 $response = $this->client->send($request);
180 } catch (RequestException $e) {
181 $this->logger->error(sprintf('PocketImport: Failed to import: %s', $e->getMessage()), ['exception' => $e]);
182
183 return false;
184 }
185
7ec2897e
JB
186 $entries = $response->json();
187
ef75e122
JB
188 if ($this->producer) {
189 $this->parseEntriesForProducer($entries['list']);
190
191 return true;
192 }
193
b1d05721 194 $this->parseEntries($entries['list']);
7ec2897e 195
252ebd60 196 return true;
7ec2897e
JB
197 }
198
199 /**
252ebd60 200 * {@inheritdoc}
ff7b031d 201 */
252ebd60 202 public function getSummary()
ff7b031d 203 {
252ebd60
JB
204 return [
205 'skipped' => $this->skippedEntries,
206 'imported' => $this->importedEntries,
207 ];
ff7b031d
NL
208 }
209
87f23b00 210 /**
252ebd60 211 * Set the Guzzle client.
87f23b00 212 *
252ebd60 213 * @param Client $client
87f23b00 214 */
252ebd60 215 public function setClient(Client $client)
87f23b00 216 {
252ebd60 217 $this->client = $client;
dda57bb9
NL
218 }
219
ff7b031d 220 /**
252ebd60
JB
221 * @see https://getpocket.com/developer/docs/v3/retrieve
222 *
ef75e122 223 * @param array $entries
ff7b031d 224 */
ef75e122 225 private function parseEntries(array $entries)
ff7b031d 226 {
7019c7cf
JB
227 $i = 1;
228
ef75e122
JB
229 foreach ($entries as $pocketEntry) {
230 $entry = $this->parseEntry($pocketEntry);
dda57bb9 231
ef75e122 232 if (null === $entry) {
dda57bb9
NL
233 continue;
234 }
235
ef75e122
JB
236 // flush every 20 entries
237 if (($i % 20) === 0) {
238 $this->em->flush();
239 $this->em->clear($entry);
240 }
19d9efab 241
ef75e122
JB
242 ++$i;
243 }
56c778b4 244
ef75e122
JB
245 $this->em->flush();
246 }
dda57bb9 247
ef75e122
JB
248 public function parseEntry(array $pocketEntry)
249 {
250 $url = isset($pocketEntry['resolved_url']) && $pocketEntry['resolved_url'] != '' ? $pocketEntry['resolved_url'] : $pocketEntry['given_url'];
252ebd60 251
ef75e122
JB
252 $existingEntry = $this->em
253 ->getRepository('WallabagCoreBundle:Entry')
254 ->findByUrlAndUserId($url, $this->user->getId());
87f23b00 255
ef75e122
JB
256 if (false !== $existingEntry) {
257 ++$this->skippedEntries;
ff7b031d 258
ef75e122
JB
259 return;
260 }
ff7b031d 261
ef75e122
JB
262 $entry = new Entry($this->user);
263 $entry = $this->fetchContent($entry, $url);
ff7b031d 264
ef75e122
JB
265 // jump to next entry in case of problem while getting content
266 if (false === $entry) {
267 ++$this->skippedEntries;
ff7b031d 268
ef75e122
JB
269 return;
270 }
56c778b4 271
ef75e122
JB
272 // 0, 1, 2 - 1 if the item is archived - 2 if the item should be deleted
273 if ($pocketEntry['status'] == 1 || $this->markAsRead) {
274 $entry->setArchived(true);
275 }
7019c7cf 276
ef75e122
JB
277 // 0 or 1 - 1 If the item is starred
278 if ($pocketEntry['favorite'] == 1) {
279 $entry->setStarred(true);
280 }
56c778b4 281
ef75e122
JB
282 $title = 'Untitled';
283 if (isset($pocketEntry['resolved_title']) && $pocketEntry['resolved_title'] != '') {
284 $title = $pocketEntry['resolved_title'];
285 } elseif (isset($pocketEntry['given_title']) && $pocketEntry['given_title'] != '') {
286 $title = $pocketEntry['given_title'];
ff7b031d
NL
287 }
288
ef75e122
JB
289 $entry->setTitle($title);
290 $entry->setUrl($url);
291
292 // 0, 1, or 2 - 1 if the item has images in it - 2 if the item is an image
293 if (isset($pocketEntry['has_image']) && $pocketEntry['has_image'] > 0 && isset($pocketEntry['images'][1])) {
294 $entry->setPreviewPicture($pocketEntry['images'][1]['src']);
295 }
296
297 if (isset($pocketEntry['tags']) && !empty($pocketEntry['tags'])) {
298 $this->contentProxy->assignTagsToEntry(
299 $entry,
300 array_keys($pocketEntry['tags'])
301 );
302 }
303
304 $this->em->persist($entry);
305 ++$this->importedEntries;
56c778b4 306
ef75e122
JB
307 return $entry;
308 }
309
310 /**
311 * Faster parse entries for Producer.
312 * We don't care to make check at this time. They'll be done by the consumer.
313 *
314 * @param array $entries
315 */
316 public function parseEntriesForProducer($entries)
317 {
318 foreach ($entries as $pocketEntry) {
319 // set userId for the producer (it won't know which user is connected)
320 $pocketEntry['userId'] = $this->user->getId();
321
322 if ($this->markAsRead) {
323 $pocketEntry['status'] = 1;
56c778b4 324 }
ef75e122
JB
325
326 ++$this->importedEntries;
327
328 $this->producer->publish(json_encode($pocketEntry));
56c778b4 329 }
ff7b031d 330 }
ff7b031d 331}