diff options
-rw-r--r-- | src/Wallabag/ApiBundle/Controller/EntryRestController.php | 211 | ||||
-rw-r--r-- | src/Wallabag/CoreBundle/Entity/Entry.php | 16 | ||||
-rw-r--r-- | src/Wallabag/CoreBundle/Helper/ContentProxy.php | 182 | ||||
-rw-r--r-- | tests/Wallabag/ApiBundle/Controller/EntryRestControllerTest.php | 63 | ||||
-rw-r--r-- | tests/Wallabag/CoreBundle/Helper/ContentProxyTest.php | 42 |
5 files changed, 347 insertions, 167 deletions
diff --git a/src/Wallabag/ApiBundle/Controller/EntryRestController.php b/src/Wallabag/ApiBundle/Controller/EntryRestController.php index ca460c84..8a206124 100644 --- a/src/Wallabag/ApiBundle/Controller/EntryRestController.php +++ b/src/Wallabag/ApiBundle/Controller/EntryRestController.php | |||
@@ -299,8 +299,8 @@ class EntryRestController extends WallabagRestController | |||
299 | * {"name"="url", "dataType"="string", "required"=true, "format"="http://www.test.com/article.html", "description"="Url for the entry."}, | 299 | * {"name"="url", "dataType"="string", "required"=true, "format"="http://www.test.com/article.html", "description"="Url for the entry."}, |
300 | * {"name"="title", "dataType"="string", "required"=false, "description"="Optional, we'll get the title from the page."}, | 300 | * {"name"="title", "dataType"="string", "required"=false, "description"="Optional, we'll get the title from the page."}, |
301 | * {"name"="tags", "dataType"="string", "required"=false, "format"="tag1,tag2,tag3", "description"="a comma-separated list of tags."}, | 301 | * {"name"="tags", "dataType"="string", "required"=false, "format"="tag1,tag2,tag3", "description"="a comma-separated list of tags."}, |
302 | * {"name"="starred", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="entry already starred"}, | ||
303 | * {"name"="archive", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="entry already archived"}, | 302 | * {"name"="archive", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="entry already archived"}, |
303 | * {"name"="starred", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="entry already starred"}, | ||
304 | * {"name"="content", "dataType"="string", "required"=false, "description"="Content of the entry"}, | 304 | * {"name"="content", "dataType"="string", "required"=false, "description"="Content of the entry"}, |
305 | * {"name"="language", "dataType"="string", "required"=false, "description"="Language of the entry"}, | 305 | * {"name"="language", "dataType"="string", "required"=false, "description"="Language of the entry"}, |
306 | * {"name"="preview_picture", "dataType"="string", "required"=false, "description"="Preview picture of the entry"}, | 306 | * {"name"="preview_picture", "dataType"="string", "required"=false, "description"="Preview picture of the entry"}, |
@@ -328,7 +328,58 @@ class EntryRestController extends WallabagRestController | |||
328 | $entry->setUrl($url); | 328 | $entry->setUrl($url); |
329 | } | 329 | } |
330 | 330 | ||
331 | $this->upsertEntry($entry, $request); | 331 | $data = $this->retrieveValueFromRequest($request); |
332 | |||
333 | try { | ||
334 | $this->get('wallabag_core.content_proxy')->updateEntry( | ||
335 | $entry, | ||
336 | $entry->getUrl(), | ||
337 | [ | ||
338 | 'title' => !empty($data['title']) ? $data['title'] : $entry->getTitle(), | ||
339 | 'html' => !empty($data['content']) ? $data['content'] : $entry->getContent(), | ||
340 | 'url' => $entry->getUrl(), | ||
341 | 'language' => !empty($data['language']) ? $data['language'] : $entry->getLanguage(), | ||
342 | 'date' => !empty($data['publishedAt']) ? $data['publishedAt'] : $entry->getPublishedAt(), | ||
343 | // faking the open graph preview picture | ||
344 | 'open_graph' => [ | ||
345 | 'og_image' => !empty($data['picture']) ? $data['picture'] : $entry->getPreviewPicture(), | ||
346 | ], | ||
347 | 'authors' => is_string($data['authors']) ? explode(',', $data['authors']) : $entry->getPublishedBy(), | ||
348 | ] | ||
349 | ); | ||
350 | } catch (\Exception $e) { | ||
351 | $this->get('logger')->error('Error while saving an entry', [ | ||
352 | 'exception' => $e, | ||
353 | 'entry' => $entry, | ||
354 | ]); | ||
355 | } | ||
356 | |||
357 | if (null !== $data['isArchived']) { | ||
358 | $entry->setArchived((bool) $data['isArchived']); | ||
359 | } | ||
360 | |||
361 | if (null !== $data['isStarred']) { | ||
362 | $entry->setStarred((bool) $data['isStarred']); | ||
363 | } | ||
364 | |||
365 | if (!empty($data['tags'])) { | ||
366 | $this->get('wallabag_core.tags_assigner')->assignTagsToEntry($entry, $data['tags']); | ||
367 | } | ||
368 | |||
369 | if (null !== $data['isPublic']) { | ||
370 | if (true === (bool) $data['isPublic'] && null === $entry->getUid()) { | ||
371 | $entry->generateUid(); | ||
372 | } elseif (false === (bool) $data['isPublic']) { | ||
373 | $entry->cleanUid(); | ||
374 | } | ||
375 | } | ||
376 | |||
377 | $em = $this->getDoctrine()->getManager(); | ||
378 | $em->persist($entry); | ||
379 | $em->flush(); | ||
380 | |||
381 | // entry saved, dispatch event about it! | ||
382 | $this->get('event_dispatcher')->dispatch(EntrySavedEvent::NAME, new EntrySavedEvent($entry)); | ||
332 | 383 | ||
333 | return $this->sendResponse($entry); | 384 | return $this->sendResponse($entry); |
334 | } | 385 | } |
@@ -361,7 +412,78 @@ class EntryRestController extends WallabagRestController | |||
361 | $this->validateAuthentication(); | 412 | $this->validateAuthentication(); |
362 | $this->validateUserAccess($entry->getUser()->getId()); | 413 | $this->validateUserAccess($entry->getUser()->getId()); |
363 | 414 | ||
364 | $this->upsertEntry($entry, $request, true); | 415 | $contentProxy = $this->get('wallabag_core.content_proxy'); |
416 | |||
417 | $data = $this->retrieveValueFromRequest($request); | ||
418 | |||
419 | // this is a special case where user want to manually update the entry content | ||
420 | // the ContentProxy will only cleanup the html | ||
421 | // and also we force to not re-fetch the content in case of error | ||
422 | if (!empty($data['content'])) { | ||
423 | try { | ||
424 | $contentProxy->updateEntry( | ||
425 | $entry, | ||
426 | $entry->getUrl(), | ||
427 | [ | ||
428 | 'html' => $data['content'], | ||
429 | ], | ||
430 | true | ||
431 | ); | ||
432 | } catch (\Exception $e) { | ||
433 | $this->get('logger')->error('Error while saving an entry', [ | ||
434 | 'exception' => $e, | ||
435 | 'entry' => $entry, | ||
436 | ]); | ||
437 | } | ||
438 | } | ||
439 | |||
440 | if (!empty($data['title'])) { | ||
441 | $entry->setTitle($data['title']); | ||
442 | } | ||
443 | |||
444 | if (!empty($data['language'])) { | ||
445 | $contentProxy->updateLanguage($entry, $data['language']); | ||
446 | } | ||
447 | |||
448 | if (!empty($data['authors']) && is_string($data['authors'])) { | ||
449 | $entry->setPublishedBy(explode(',', $data['authors'])); | ||
450 | } | ||
451 | |||
452 | if (!empty($data['picture'])) { | ||
453 | $contentProxy->updatePreviewPicture($entry, $data['picture']); | ||
454 | } | ||
455 | |||
456 | if (!empty($data['publishedAt'])) { | ||
457 | $contentProxy->updatePublishedAt($entry, $data['publishedAt']); | ||
458 | } | ||
459 | |||
460 | if (null !== $data['isArchived']) { | ||
461 | $entry->setArchived((bool) $data['isArchived']); | ||
462 | } | ||
463 | |||
464 | if (null !== $data['isStarred']) { | ||
465 | $entry->setStarred((bool) $data['isStarred']); | ||
466 | } | ||
467 | |||
468 | if (!empty($data['tags'])) { | ||
469 | $entry->removeAllTags(); | ||
470 | $this->get('wallabag_core.tags_assigner')->assignTagsToEntry($entry, $data['tags']); | ||
471 | } | ||
472 | |||
473 | if (null !== $data['isPublic']) { | ||
474 | if (true === (bool) $data['isPublic'] && null === $entry->getUid()) { | ||
475 | $entry->generateUid(); | ||
476 | } elseif (false === (bool) $data['isPublic']) { | ||
477 | $entry->cleanUid(); | ||
478 | } | ||
479 | } | ||
480 | |||
481 | $em = $this->getDoctrine()->getManager(); | ||
482 | $em->persist($entry); | ||
483 | $em->flush(); | ||
484 | |||
485 | // entry saved, dispatch event about it! | ||
486 | $this->get('event_dispatcher')->dispatch(EntrySavedEvent::NAME, new EntrySavedEvent($entry)); | ||
365 | 487 | ||
366 | return $this->sendResponse($entry); | 488 | return $this->sendResponse($entry); |
367 | } | 489 | } |
@@ -634,76 +756,27 @@ class EntryRestController extends WallabagRestController | |||
634 | } | 756 | } |
635 | 757 | ||
636 | /** | 758 | /** |
637 | * Update or Insert a new entry. | 759 | * Retrieve value from the request. |
760 | * Used for POST & PATCH on a an entry. | ||
638 | * | 761 | * |
639 | * @param Entry $entry | ||
640 | * @param Request $request | 762 | * @param Request $request |
641 | * @param bool $disableContentUpdate If we don't want the content to be update by fetching the url (used when patching instead of posting) | 763 | * |
764 | * @return array | ||
642 | */ | 765 | */ |
643 | private function upsertEntry(Entry $entry, Request $request, $disableContentUpdate = false) | 766 | private function retrieveValueFromRequest(Request $request) |
644 | { | 767 | { |
645 | $title = $request->request->get('title'); | 768 | return [ |
646 | $tags = $request->request->get('tags', []); | 769 | 'title' => $request->request->get('title'), |
647 | $isArchived = $request->request->get('archive'); | 770 | 'tags' => $request->request->get('tags', []), |
648 | $isStarred = $request->request->get('starred'); | 771 | 'isArchived' => $request->request->get('archive'), |
649 | $isPublic = $request->request->get('public'); | 772 | 'isStarred' => $request->request->get('starred'), |
650 | $content = $request->request->get('content'); | 773 | 'isPublic' => $request->request->get('public'), |
651 | $language = $request->request->get('language'); | 774 | 'content' => $request->request->get('content'), |
652 | $picture = $request->request->get('preview_picture'); | 775 | 'language' => $request->request->get('language'), |
653 | $publishedAt = $request->request->get('published_at'); | 776 | 'picture' => $request->request->get('preview_picture'), |
654 | $authors = $request->request->get('authors', ''); | 777 | 'publishedAt' => $request->request->get('published_at'), |
655 | 778 | 'authors' => $request->request->get('authors', ''), | |
656 | try { | 779 | ]; |
657 | $this->get('wallabag_core.content_proxy')->updateEntry( | ||
658 | $entry, | ||
659 | $entry->getUrl(), | ||
660 | [ | ||
661 | 'title' => !empty($title) ? $title : $entry->getTitle(), | ||
662 | 'html' => !empty($content) ? $content : $entry->getContent(), | ||
663 | 'url' => $entry->getUrl(), | ||
664 | 'language' => !empty($language) ? $language : $entry->getLanguage(), | ||
665 | 'date' => !empty($publishedAt) ? $publishedAt : $entry->getPublishedAt(), | ||
666 | // faking the open graph preview picture | ||
667 | 'open_graph' => [ | ||
668 | 'og_image' => !empty($picture) ? $picture : $entry->getPreviewPicture(), | ||
669 | ], | ||
670 | 'authors' => is_string($authors) ? explode(',', $authors) : $entry->getPublishedBy(), | ||
671 | ], | ||
672 | $disableContentUpdate | ||
673 | ); | ||
674 | } catch (\Exception $e) { | ||
675 | $this->get('logger')->error('Error while saving an entry', [ | ||
676 | 'exception' => $e, | ||
677 | 'entry' => $entry, | ||
678 | ]); | ||
679 | } | ||
680 | |||
681 | if (null !== $isArchived) { | ||
682 | $entry->setArchived((bool) $isArchived); | ||
683 | } | ||
684 | |||
685 | if (null !== $isStarred) { | ||
686 | $entry->setStarred((bool) $isStarred); | ||
687 | } | ||
688 | |||
689 | if (!empty($tags)) { | ||
690 | $this->get('wallabag_core.tags_assigner')->assignTagsToEntry($entry, $tags); | ||
691 | } | ||
692 | |||
693 | if (null !== $isPublic) { | ||
694 | if (true === (bool) $isPublic && null === $entry->getUid()) { | ||
695 | $entry->generateUid(); | ||
696 | } elseif (false === (bool) $isPublic) { | ||
697 | $entry->cleanUid(); | ||
698 | } | ||
699 | } | ||
700 | |||
701 | $em = $this->getDoctrine()->getManager(); | ||
702 | $em->persist($entry); | ||
703 | $em->flush(); | ||
704 | |||
705 | // entry saved, dispatch event about it! | ||
706 | $this->get('event_dispatcher')->dispatch(EntrySavedEvent::NAME, new EntrySavedEvent($entry)); | ||
707 | } | 780 | } |
708 | 781 | ||
709 | /** | 782 | /** |
diff --git a/src/Wallabag/CoreBundle/Entity/Entry.php b/src/Wallabag/CoreBundle/Entity/Entry.php index 581e8906..cba72d31 100644 --- a/src/Wallabag/CoreBundle/Entity/Entry.php +++ b/src/Wallabag/CoreBundle/Entity/Entry.php | |||
@@ -593,6 +593,11 @@ class Entry | |||
593 | $tag->addEntry($this); | 593 | $tag->addEntry($this); |
594 | } | 594 | } |
595 | 595 | ||
596 | /** | ||
597 | * Remove the given tag from the entry (if the tag is associated). | ||
598 | * | ||
599 | * @param Tag $tag | ||
600 | */ | ||
596 | public function removeTag(Tag $tag) | 601 | public function removeTag(Tag $tag) |
597 | { | 602 | { |
598 | if (!$this->tags->contains($tag)) { | 603 | if (!$this->tags->contains($tag)) { |
@@ -604,6 +609,17 @@ class Entry | |||
604 | } | 609 | } |
605 | 610 | ||
606 | /** | 611 | /** |
612 | * Remove all assigned tags from the entry. | ||
613 | */ | ||
614 | public function removeAllTags() | ||
615 | { | ||
616 | foreach ($this->tags as $tag) { | ||
617 | $this->tags->removeElement($tag); | ||
618 | $tag->removeEntry($this); | ||
619 | } | ||
620 | } | ||
621 | |||
622 | /** | ||
607 | * Set previewPicture. | 623 | * Set previewPicture. |
608 | * | 624 | * |
609 | * @param string $previewPicture | 625 | * @param string $previewPicture |
diff --git a/src/Wallabag/CoreBundle/Helper/ContentProxy.php b/src/Wallabag/CoreBundle/Helper/ContentProxy.php index ddecd6f4..656ac6ee 100644 --- a/src/Wallabag/CoreBundle/Helper/ContentProxy.php +++ b/src/Wallabag/CoreBundle/Helper/ContentProxy.php | |||
@@ -67,6 +67,76 @@ class ContentProxy | |||
67 | } | 67 | } |
68 | 68 | ||
69 | /** | 69 | /** |
70 | * Use a Symfony validator to ensure the language is well formatted. | ||
71 | * | ||
72 | * @param Entry $entry | ||
73 | * @param string $value Language to validate and save | ||
74 | */ | ||
75 | public function updateLanguage(Entry $entry, $value) | ||
76 | { | ||
77 | // some lang are defined as fr-FR, es-ES. | ||
78 | // replacing - by _ might increase language support | ||
79 | $value = str_replace('-', '_', $value); | ||
80 | |||
81 | $errors = $this->validator->validate( | ||
82 | $value, | ||
83 | (new LocaleConstraint()) | ||
84 | ); | ||
85 | |||
86 | if (0 === count($errors)) { | ||
87 | $entry->setLanguage($value); | ||
88 | |||
89 | return; | ||
90 | } | ||
91 | |||
92 | $this->logger->warning('Language validation failed. ' . (string) $errors); | ||
93 | } | ||
94 | |||
95 | /** | ||
96 | * Use a Symfony validator to ensure the preview picture is a real url. | ||
97 | * | ||
98 | * @param Entry $entry | ||
99 | * @param string $value URL to validate and save | ||
100 | */ | ||
101 | public function updatePreviewPicture(Entry $entry, $value) | ||
102 | { | ||
103 | $errors = $this->validator->validate( | ||
104 | $value, | ||
105 | (new UrlConstraint()) | ||
106 | ); | ||
107 | |||
108 | if (0 === count($errors)) { | ||
109 | $entry->setPreviewPicture($value); | ||
110 | |||
111 | return; | ||
112 | } | ||
113 | |||
114 | $this->logger->warning('PreviewPicture validation failed. ' . (string) $errors); | ||
115 | } | ||
116 | |||
117 | /** | ||
118 | * Update date. | ||
119 | * | ||
120 | * @param Entry $entry | ||
121 | * @param string $value Date to validate and save | ||
122 | */ | ||
123 | public function updatePublishedAt(Entry $entry, $value) | ||
124 | { | ||
125 | $date = $value; | ||
126 | |||
127 | // is it a timestamp? | ||
128 | if (filter_var($date, FILTER_VALIDATE_INT) !== false) { | ||
129 | $date = '@' . $value; | ||
130 | } | ||
131 | |||
132 | try { | ||
133 | $entry->setPublishedAt(new \DateTime($date)); | ||
134 | } catch (\Exception $e) { | ||
135 | $this->logger->warning('Error while defining date', ['e' => $e, 'url' => $entry->getUrl(), 'date' => $value]); | ||
136 | } | ||
137 | } | ||
138 | |||
139 | /** | ||
70 | * Stock entry with fetched or imported content. | 140 | * Stock entry with fetched or imported content. |
71 | * Will fall back to OpenGraph data if available. | 141 | * Will fall back to OpenGraph data if available. |
72 | * | 142 | * |
@@ -75,9 +145,17 @@ class ContentProxy | |||
75 | */ | 145 | */ |
76 | private function stockEntry(Entry $entry, array $content) | 146 | private function stockEntry(Entry $entry, array $content) |
77 | { | 147 | { |
78 | $title = $content['title']; | 148 | $entry->setUrl($content['url']); |
79 | if (!$title && !empty($content['open_graph']['og_title'])) { | 149 | |
80 | $title = $content['open_graph']['og_title']; | 150 | $domainName = parse_url($entry->getUrl(), PHP_URL_HOST); |
151 | if (false !== $domainName) { | ||
152 | $entry->setDomainName($domainName); | ||
153 | } | ||
154 | |||
155 | if (!empty($content['title'])) { | ||
156 | $entry->setTitle($content['title']); | ||
157 | } elseif (!empty($content['open_graph']['og_title'])) { | ||
158 | $entry->setTitle($content['open_graph']['og_title']); | ||
81 | } | 159 | } |
82 | 160 | ||
83 | $html = $content['html']; | 161 | $html = $content['html']; |
@@ -90,24 +168,11 @@ class ContentProxy | |||
90 | } | 168 | } |
91 | } | 169 | } |
92 | 170 | ||
93 | $entry->setUrl($content['url']); | ||
94 | $entry->setTitle($title); | ||
95 | $entry->setContent($html); | 171 | $entry->setContent($html); |
96 | $entry->setHttpStatus(isset($content['status']) ? $content['status'] : ''); | 172 | $entry->setReadingTime(Utils::getReadingTime($html)); |
97 | |||
98 | if (!empty($content['date'])) { | ||
99 | $date = $content['date']; | ||
100 | |||
101 | // is it a timestamp? | ||
102 | if (filter_var($date, FILTER_VALIDATE_INT) !== false) { | ||
103 | $date = '@' . $content['date']; | ||
104 | } | ||
105 | 173 | ||
106 | try { | 174 | if (!empty($content['status'])) { |
107 | $entry->setPublishedAt(new \DateTime($date)); | 175 | $entry->setHttpStatus($content['status']); |
108 | } catch (\Exception $e) { | ||
109 | $this->logger->warning('Error while defining date', ['e' => $e, 'url' => $content['url'], 'date' => $content['date']]); | ||
110 | } | ||
111 | } | 176 | } |
112 | 177 | ||
113 | if (!empty($content['authors']) && is_array($content['authors'])) { | 178 | if (!empty($content['authors']) && is_array($content['authors'])) { |
@@ -118,30 +183,25 @@ class ContentProxy | |||
118 | $entry->setHeaders($content['all_headers']); | 183 | $entry->setHeaders($content['all_headers']); |
119 | } | 184 | } |
120 | 185 | ||
121 | $this->validateAndSetLanguage( | 186 | if (!empty($content['date'])) { |
122 | $entry, | 187 | $this->updatePublishedAt($entry, $content['date']); |
123 | isset($content['language']) ? $content['language'] : null | 188 | } |
124 | ); | ||
125 | 189 | ||
126 | $this->validateAndSetPreviewPicture( | 190 | if (!empty($content['language'])) { |
127 | $entry, | 191 | $this->updateLanguage($entry, $content['language']); |
128 | isset($content['open_graph']['og_image']) ? $content['open_graph']['og_image'] : null | 192 | } |
129 | ); | 193 | |
194 | if (!empty($content['open_graph']['og_image'])) { | ||
195 | $this->updatePreviewPicture($entry, $content['open_graph']['og_image']); | ||
196 | } | ||
130 | 197 | ||
131 | // if content is an image, define it as a preview too | 198 | // if content is an image, define it as a preview too |
132 | if (!empty($content['content_type']) && in_array($this->mimeGuesser->guess($content['content_type']), ['jpeg', 'jpg', 'gif', 'png'], true)) { | 199 | if (!empty($content['content_type']) && in_array($this->mimeGuesser->guess($content['content_type']), ['jpeg', 'jpg', 'gif', 'png'], true)) { |
133 | $this->validateAndSetPreviewPicture( | 200 | $this->updatePreviewPicture($entry, $content['url']); |
134 | $entry, | ||
135 | $content['url'] | ||
136 | ); | ||
137 | } | 201 | } |
138 | 202 | ||
139 | $entry->setMimetype(isset($content['content_type']) ? $content['content_type'] : ''); | 203 | if (!empty($content['content_type'])) { |
140 | $entry->setReadingTime(Utils::getReadingTime($html)); | 204 | $entry->setMimetype($content['content_type']); |
141 | |||
142 | $domainName = parse_url($entry->getUrl(), PHP_URL_HOST); | ||
143 | if (false !== $domainName) { | ||
144 | $entry->setDomainName($domainName); | ||
145 | } | 205 | } |
146 | 206 | ||
147 | try { | 207 | try { |
@@ -165,52 +225,4 @@ class ContentProxy | |||
165 | { | 225 | { |
166 | return !empty($content['title']) && !empty($content['html']) && !empty($content['url']); | 226 | return !empty($content['title']) && !empty($content['html']) && !empty($content['url']); |
167 | } | 227 | } |
168 | |||
169 | /** | ||
170 | * Use a Symfony validator to ensure the language is well formatted. | ||
171 | * | ||
172 | * @param Entry $entry | ||
173 | * @param string $value Language to validate | ||
174 | */ | ||
175 | private function validateAndSetLanguage($entry, $value) | ||
176 | { | ||
177 | // some lang are defined as fr-FR, es-ES. | ||
178 | // replacing - by _ might increase language support | ||
179 | $value = str_replace('-', '_', $value); | ||
180 | |||
181 | $errors = $this->validator->validate( | ||
182 | $value, | ||
183 | (new LocaleConstraint()) | ||
184 | ); | ||
185 | |||
186 | if (0 === count($errors)) { | ||
187 | $entry->setLanguage($value); | ||
188 | |||
189 | return; | ||
190 | } | ||
191 | |||
192 | $this->logger->warning('Language validation failed. ' . (string) $errors); | ||
193 | } | ||
194 | |||
195 | /** | ||
196 | * Use a Symfony validator to ensure the preview picture is a real url. | ||
197 | * | ||
198 | * @param Entry $entry | ||
199 | * @param string $value URL to validate | ||
200 | */ | ||
201 | private function validateAndSetPreviewPicture($entry, $value) | ||
202 | { | ||
203 | $errors = $this->validator->validate( | ||
204 | $value, | ||
205 | (new UrlConstraint()) | ||
206 | ); | ||
207 | |||
208 | if (0 === count($errors)) { | ||
209 | $entry->setPreviewPicture($value); | ||
210 | |||
211 | return; | ||
212 | } | ||
213 | |||
214 | $this->logger->warning('PreviewPicture validation failed. ' . (string) $errors); | ||
215 | } | ||
216 | } | 228 | } |
diff --git a/tests/Wallabag/ApiBundle/Controller/EntryRestControllerTest.php b/tests/Wallabag/ApiBundle/Controller/EntryRestControllerTest.php index ae4af4cd..c76be13d 100644 --- a/tests/Wallabag/ApiBundle/Controller/EntryRestControllerTest.php +++ b/tests/Wallabag/ApiBundle/Controller/EntryRestControllerTest.php | |||
@@ -519,9 +519,6 @@ class EntryRestControllerTest extends WallabagApiTestCase | |||
519 | $this->markTestSkipped('No content found in db.'); | 519 | $this->markTestSkipped('No content found in db.'); |
520 | } | 520 | } |
521 | 521 | ||
522 | // hydrate the tags relations | ||
523 | $nbTags = count($entry->getTags()); | ||
524 | |||
525 | $this->client->request('PATCH', '/api/entries/' . $entry->getId() . '.json', [ | 522 | $this->client->request('PATCH', '/api/entries/' . $entry->getId() . '.json', [ |
526 | 'title' => 'New awesome title', | 523 | 'title' => 'New awesome title', |
527 | 'tags' => 'new tag ' . uniqid(), | 524 | 'tags' => 'new tag ' . uniqid(), |
@@ -532,6 +529,7 @@ class EntryRestControllerTest extends WallabagApiTestCase | |||
532 | 'authors' => 'bob,sponge', | 529 | 'authors' => 'bob,sponge', |
533 | 'content' => 'awesome', | 530 | 'content' => 'awesome', |
534 | 'public' => 0, | 531 | 'public' => 0, |
532 | 'published_at' => 1488833381, | ||
535 | ]); | 533 | ]); |
536 | 534 | ||
537 | $this->assertSame(200, $this->client->getResponse()->getStatusCode()); | 535 | $this->assertSame(200, $this->client->getResponse()->getStatusCode()); |
@@ -541,7 +539,7 @@ class EntryRestControllerTest extends WallabagApiTestCase | |||
541 | $this->assertSame($entry->getId(), $content['id']); | 539 | $this->assertSame($entry->getId(), $content['id']); |
542 | $this->assertSame($entry->getUrl(), $content['url']); | 540 | $this->assertSame($entry->getUrl(), $content['url']); |
543 | $this->assertSame('New awesome title', $content['title']); | 541 | $this->assertSame('New awesome title', $content['title']); |
544 | $this->assertGreaterThan($nbTags, count($content['tags'])); | 542 | $this->assertGreaterThanOrEqual(1, count($content['tags']), 'We force only one tag'); |
545 | $this->assertSame(1, $content['user_id']); | 543 | $this->assertSame(1, $content['user_id']); |
546 | $this->assertSame('de_AT', $content['language']); | 544 | $this->assertSame('de_AT', $content['language']); |
547 | $this->assertSame('http://preview.io/picture.jpg', $content['preview_picture']); | 545 | $this->assertSame('http://preview.io/picture.jpg', $content['preview_picture']); |
@@ -549,6 +547,7 @@ class EntryRestControllerTest extends WallabagApiTestCase | |||
549 | $this->assertContains('bob', $content['published_by']); | 547 | $this->assertContains('bob', $content['published_by']); |
550 | $this->assertSame('awesome', $content['content']); | 548 | $this->assertSame('awesome', $content['content']); |
551 | $this->assertFalse($content['is_public'], 'Entry is no more shared'); | 549 | $this->assertFalse($content['is_public'], 'Entry is no more shared'); |
550 | $this->assertContains('2017-03-06', $content['published_at']); | ||
552 | } | 551 | } |
553 | 552 | ||
554 | public function testPatchEntryWithoutQuotes() | 553 | public function testPatchEntryWithoutQuotes() |
@@ -562,8 +561,8 @@ class EntryRestControllerTest extends WallabagApiTestCase | |||
562 | $this->markTestSkipped('No content found in db.'); | 561 | $this->markTestSkipped('No content found in db.'); |
563 | } | 562 | } |
564 | 563 | ||
565 | // hydrate the tags relations | 564 | $previousContent = $entry->getContent(); |
566 | $nbTags = count($entry->getTags()); | 565 | $previousLanguage = $entry->getLanguage(); |
567 | 566 | ||
568 | $this->client->request('PATCH', '/api/entries/' . $entry->getId() . '.json', [ | 567 | $this->client->request('PATCH', '/api/entries/' . $entry->getId() . '.json', [ |
569 | 'title' => 'New awesome title', | 568 | 'title' => 'New awesome title', |
@@ -579,9 +578,10 @@ class EntryRestControllerTest extends WallabagApiTestCase | |||
579 | 578 | ||
580 | $this->assertSame($entry->getId(), $content['id']); | 579 | $this->assertSame($entry->getId(), $content['id']); |
581 | $this->assertSame($entry->getUrl(), $content['url']); | 580 | $this->assertSame($entry->getUrl(), $content['url']); |
582 | $this->assertSame('New awesome title', $content['title']); | 581 | $this->assertGreaterThanOrEqual(1, count($content['tags']), 'We force only one tag'); |
583 | $this->assertGreaterThan($nbTags, count($content['tags'])); | ||
584 | $this->assertEmpty($content['published_by'], 'Authors were not saved because of an array instead of a string'); | 582 | $this->assertEmpty($content['published_by'], 'Authors were not saved because of an array instead of a string'); |
583 | $this->assertSame($previousContent, $content['content'], 'Ensure content has not moved'); | ||
584 | $this->assertSame($previousLanguage, $content['language'], 'Ensure language has not moved'); | ||
585 | } | 585 | } |
586 | 586 | ||
587 | public function testGetTagsEntry() | 587 | public function testGetTagsEntry() |
@@ -727,6 +727,8 @@ class EntryRestControllerTest extends WallabagApiTestCase | |||
727 | $this->markTestSkipped('No content found in db.'); | 727 | $this->markTestSkipped('No content found in db.'); |
728 | } | 728 | } |
729 | 729 | ||
730 | $previousTitle = $entry->getTitle(); | ||
731 | |||
730 | $this->client->request('PATCH', '/api/entries/' . $entry->getId() . '.json', [ | 732 | $this->client->request('PATCH', '/api/entries/' . $entry->getId() . '.json', [ |
731 | 'title' => $entry->getTitle() . '++', | 733 | 'title' => $entry->getTitle() . '++', |
732 | ]); | 734 | ]); |
@@ -736,6 +738,7 @@ class EntryRestControllerTest extends WallabagApiTestCase | |||
736 | $content = json_decode($this->client->getResponse()->getContent(), true); | 738 | $content = json_decode($this->client->getResponse()->getContent(), true); |
737 | 739 | ||
738 | $this->assertSame(1, $content['is_archived']); | 740 | $this->assertSame(1, $content['is_archived']); |
741 | $this->assertSame($previousTitle . '++', $content['title']); | ||
739 | } | 742 | } |
740 | 743 | ||
741 | public function testSaveIsStarredAfterPatch() | 744 | public function testSaveIsStarredAfterPatch() |
@@ -907,6 +910,17 @@ class EntryRestControllerTest extends WallabagApiTestCase | |||
907 | $this->assertCount(4, $tags); | 910 | $this->assertCount(4, $tags); |
908 | } | 911 | } |
909 | 912 | ||
913 | public function testPostEntriesTagsListActionNoList() | ||
914 | { | ||
915 | $this->client->request('POST', '/api/entries/tags/lists?list=' . json_encode([])); | ||
916 | |||
917 | $this->assertSame(200, $this->client->getResponse()->getStatusCode()); | ||
918 | |||
919 | $content = json_decode($this->client->getResponse()->getContent(), true); | ||
920 | |||
921 | $this->assertEmpty($content); | ||
922 | } | ||
923 | |||
910 | public function testDeleteEntriesTagsListAction() | 924 | public function testDeleteEntriesTagsListAction() |
911 | { | 925 | { |
912 | $em = $this->client->getContainer()->get('doctrine.orm.entity_manager'); | 926 | $em = $this->client->getContainer()->get('doctrine.orm.entity_manager'); |
@@ -933,6 +947,17 @@ class EntryRestControllerTest extends WallabagApiTestCase | |||
933 | $this->assertCount(0, $entry->getTags()); | 947 | $this->assertCount(0, $entry->getTags()); |
934 | } | 948 | } |
935 | 949 | ||
950 | public function testDeleteEntriesTagsListActionNoList() | ||
951 | { | ||
952 | $this->client->request('DELETE', '/api/entries/tags/list?list=' . json_encode([])); | ||
953 | |||
954 | $this->assertSame(200, $this->client->getResponse()->getStatusCode()); | ||
955 | |||
956 | $content = json_decode($this->client->getResponse()->getContent(), true); | ||
957 | |||
958 | $this->assertEmpty($content); | ||
959 | } | ||
960 | |||
936 | public function testPostEntriesListAction() | 961 | public function testPostEntriesListAction() |
937 | { | 962 | { |
938 | $list = [ | 963 | $list = [ |
@@ -953,6 +978,17 @@ class EntryRestControllerTest extends WallabagApiTestCase | |||
953 | $this->assertSame('http://0.0.0.0/entry2', $content[1]['url']); | 978 | $this->assertSame('http://0.0.0.0/entry2', $content[1]['url']); |
954 | } | 979 | } |
955 | 980 | ||
981 | public function testPostEntriesListActionWithNoUrls() | ||
982 | { | ||
983 | $this->client->request('POST', '/api/entries/lists?urls=' . json_encode([])); | ||
984 | |||
985 | $this->assertSame(200, $this->client->getResponse()->getStatusCode()); | ||
986 | |||
987 | $content = json_decode($this->client->getResponse()->getContent(), true); | ||
988 | |||
989 | $this->assertEmpty($content); | ||
990 | } | ||
991 | |||
956 | public function testDeleteEntriesListAction() | 992 | public function testDeleteEntriesListAction() |
957 | { | 993 | { |
958 | $em = $this->client->getContainer()->get('doctrine.orm.entity_manager'); | 994 | $em = $this->client->getContainer()->get('doctrine.orm.entity_manager'); |
@@ -978,6 +1014,17 @@ class EntryRestControllerTest extends WallabagApiTestCase | |||
978 | $this->assertSame('http://0.0.0.0/test-entry-not-exist', $content[1]['url']); | 1014 | $this->assertSame('http://0.0.0.0/test-entry-not-exist', $content[1]['url']); |
979 | } | 1015 | } |
980 | 1016 | ||
1017 | public function testDeleteEntriesListActionWithNoUrls() | ||
1018 | { | ||
1019 | $this->client->request('DELETE', '/api/entries/list?urls=' . json_encode([])); | ||
1020 | |||
1021 | $this->assertSame(200, $this->client->getResponse()->getStatusCode()); | ||
1022 | |||
1023 | $content = json_decode($this->client->getResponse()->getContent(), true); | ||
1024 | |||
1025 | $this->assertEmpty($content); | ||
1026 | } | ||
1027 | |||
981 | public function testLimitBulkAction() | 1028 | public function testLimitBulkAction() |
982 | { | 1029 | { |
983 | $list = [ | 1030 | $list = [ |
diff --git a/tests/Wallabag/CoreBundle/Helper/ContentProxyTest.php b/tests/Wallabag/CoreBundle/Helper/ContentProxyTest.php index c63671c4..f94c2137 100644 --- a/tests/Wallabag/CoreBundle/Helper/ContentProxyTest.php +++ b/tests/Wallabag/CoreBundle/Helper/ContentProxyTest.php | |||
@@ -221,12 +221,9 @@ class ContentProxyTest extends \PHPUnit_Framework_TestCase | |||
221 | ->method('tag'); | 221 | ->method('tag'); |
222 | 222 | ||
223 | $validator = $this->getValidator(); | 223 | $validator = $this->getValidator(); |
224 | $validator->expects($this->exactly(2)) | 224 | $validator->expects($this->once()) |
225 | ->method('validate') | 225 | ->method('validate') |
226 | ->will($this->onConsecutiveCalls( | 226 | ->willReturn(new ConstraintViolationList([new ConstraintViolation('oops', 'oops', [], 'oops', 'language', 'dontexist')])); |
227 | new ConstraintViolationList([new ConstraintViolation('oops', 'oops', [], 'oops', 'language', 'dontexist')]), | ||
228 | new ConstraintViolationList() | ||
229 | )); | ||
230 | 227 | ||
231 | $graby = $this->getMockBuilder('Graby\Graby') | 228 | $graby = $this->getMockBuilder('Graby\Graby') |
232 | ->setMethods(['fetchContent']) | 229 | ->setMethods(['fetchContent']) |
@@ -498,6 +495,41 @@ class ContentProxyTest extends \PHPUnit_Framework_TestCase | |||
498 | $this->assertSame('1.1.1.1', $entry->getDomainName()); | 495 | $this->assertSame('1.1.1.1', $entry->getDomainName()); |
499 | } | 496 | } |
500 | 497 | ||
498 | public function testWithImageAsContent() | ||
499 | { | ||
500 | $tagger = $this->getTaggerMock(); | ||
501 | $tagger->expects($this->once()) | ||
502 | ->method('tag'); | ||
503 | |||
504 | $graby = $this->getMockBuilder('Graby\Graby') | ||
505 | ->setMethods(['fetchContent']) | ||
506 | ->disableOriginalConstructor() | ||
507 | ->getMock(); | ||
508 | |||
509 | $graby->expects($this->any()) | ||
510 | ->method('fetchContent') | ||
511 | ->willReturn([ | ||
512 | 'html' => '<p><img src="http://1.1.1.1/image.jpg" /></p>', | ||
513 | 'title' => 'this is my title', | ||
514 | 'url' => 'http://1.1.1.1/image.jpg', | ||
515 | 'content_type' => 'image/jpeg', | ||
516 | 'status' => '200', | ||
517 | 'open_graph' => [], | ||
518 | ]); | ||
519 | |||
520 | $proxy = new ContentProxy($graby, $tagger, $this->getValidator(), $this->getLogger(), $this->fetchingErrorMessage); | ||
521 | $entry = new Entry(new User()); | ||
522 | $proxy->updateEntry($entry, 'http://0.0.0.0'); | ||
523 | |||
524 | $this->assertSame('http://1.1.1.1/image.jpg', $entry->getUrl()); | ||
525 | $this->assertSame('this is my title', $entry->getTitle()); | ||
526 | $this->assertContains('http://1.1.1.1/image.jpg', $entry->getContent()); | ||
527 | $this->assertSame('http://1.1.1.1/image.jpg', $entry->getPreviewPicture()); | ||
528 | $this->assertSame('image/jpeg', $entry->getMimetype()); | ||
529 | $this->assertSame('200', $entry->getHttpStatus()); | ||
530 | $this->assertSame('1.1.1.1', $entry->getDomainName()); | ||
531 | } | ||
532 | |||
501 | private function getTaggerMock() | 533 | private function getTaggerMock() |
502 | { | 534 | { |
503 | return $this->getMockBuilder(RuleBasedTagger::class) | 535 | return $this->getMockBuilder(RuleBasedTagger::class) |