diff options
author | Nicolas LÅ“uillet <nicolas@loeuillet.org> | 2015-09-11 20:17:42 +0200 |
---|---|---|
committer | Nicolas LÅ“uillet <nicolas@loeuillet.org> | 2015-09-11 20:17:42 +0200 |
commit | 9c08a891f9bb90bc3f23a575a734283c1ee00ba1 (patch) | |
tree | f5e90765bdd2b923cbd008722e12be25028bdf6d /src/Wallabag | |
parent | 49e564ec159371d9fa751ba75f9f1c555d108179 (diff) | |
parent | f1e29e69cb0ba5a0f05190c62e7a4afd43d03436 (diff) | |
download | wallabag-9c08a891f9bb90bc3f23a575a734283c1ee00ba1.tar.gz wallabag-9c08a891f9bb90bc3f23a575a734283c1ee00ba1.tar.zst wallabag-9c08a891f9bb90bc3f23a575a734283c1ee00ba1.zip |
Merge pull request #1397 from wallabag/v2-graby
Integrate graby
Diffstat (limited to 'src/Wallabag')
10 files changed, 192 insertions, 171 deletions
diff --git a/src/Wallabag/ApiBundle/Controller/WallabagRestController.php b/src/Wallabag/ApiBundle/Controller/WallabagRestController.php index 02a6de64..349229f3 100644 --- a/src/Wallabag/ApiBundle/Controller/WallabagRestController.php +++ b/src/Wallabag/ApiBundle/Controller/WallabagRestController.php | |||
@@ -8,7 +8,6 @@ use Symfony\Component\HttpFoundation\Request; | |||
8 | use Symfony\Component\HttpFoundation\Response; | 8 | use Symfony\Component\HttpFoundation\Response; |
9 | use Wallabag\CoreBundle\Entity\Entry; | 9 | use Wallabag\CoreBundle\Entity\Entry; |
10 | use Wallabag\CoreBundle\Entity\Tag; | 10 | use Wallabag\CoreBundle\Entity\Tag; |
11 | use Wallabag\CoreBundle\Service\Extractor; | ||
12 | use Hateoas\Configuration\Route; | 11 | use Hateoas\Configuration\Route; |
13 | use Hateoas\Representation\Factory\PagerfantaFactory; | 12 | use Hateoas\Representation\Factory\PagerfantaFactory; |
14 | 13 | ||
@@ -147,11 +146,10 @@ class WallabagRestController extends Controller | |||
147 | { | 146 | { |
148 | $url = $request->request->get('url'); | 147 | $url = $request->request->get('url'); |
149 | 148 | ||
150 | $content = Extractor::extract($url); | 149 | $entry = $this->get('wallabag_core.content_proxy')->updateEntry( |
151 | $entry = new Entry($this->getUser()); | 150 | new Entry($this->getUser()), |
152 | $entry->setUrl($url); | 151 | $url |
153 | $entry->setTitle($request->request->get('title') ?: $content->getTitle()); | 152 | ); |
154 | $entry->setContent($content->getBody()); | ||
155 | 153 | ||
156 | $tags = $request->request->get('tags', ''); | 154 | $tags = $request->request->get('tags', ''); |
157 | if (!empty($tags)) { | 155 | if (!empty($tags)) { |
diff --git a/src/Wallabag/CoreBundle/Controller/EntryController.php b/src/Wallabag/CoreBundle/Controller/EntryController.php index a77489e2..b9e4e67e 100644 --- a/src/Wallabag/CoreBundle/Controller/EntryController.php +++ b/src/Wallabag/CoreBundle/Controller/EntryController.php | |||
@@ -6,7 +6,6 @@ use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; | |||
6 | use Symfony\Bundle\FrameworkBundle\Controller\Controller; | 6 | use Symfony\Bundle\FrameworkBundle\Controller\Controller; |
7 | use Symfony\Component\HttpFoundation\Request; | 7 | use Symfony\Component\HttpFoundation\Request; |
8 | use Wallabag\CoreBundle\Entity\Entry; | 8 | use Wallabag\CoreBundle\Entity\Entry; |
9 | use Wallabag\CoreBundle\Service\Extractor; | ||
10 | use Wallabag\CoreBundle\Form\Type\NewEntryType; | 9 | use Wallabag\CoreBundle\Form\Type\NewEntryType; |
11 | use Wallabag\CoreBundle\Form\Type\EditEntryType; | 10 | use Wallabag\CoreBundle\Form\Type\EditEntryType; |
12 | use Wallabag\CoreBundle\Filter\EntryFilterType; | 11 | use Wallabag\CoreBundle\Filter\EntryFilterType; |
@@ -31,10 +30,7 @@ class EntryController extends Controller | |||
31 | $form->handleRequest($request); | 30 | $form->handleRequest($request); |
32 | 31 | ||
33 | if ($form->isValid()) { | 32 | if ($form->isValid()) { |
34 | $content = Extractor::extract($entry->getUrl()); | 33 | $entry = $this->get('wallabag_core.content_proxy')->updateEntry($entry, $entry->getUrl()); |
35 | |||
36 | $entry->setTitle($content->getTitle()); | ||
37 | $entry->setContent($content->getBody()); | ||
38 | 34 | ||
39 | $em = $this->getDoctrine()->getManager(); | 35 | $em = $this->getDoctrine()->getManager(); |
40 | $em->persist($entry); | 36 | $em->persist($entry); |
diff --git a/src/Wallabag/CoreBundle/Entity/Entry.php b/src/Wallabag/CoreBundle/Entity/Entry.php index f88d189d..e684c9b1 100644 --- a/src/Wallabag/CoreBundle/Entity/Entry.php +++ b/src/Wallabag/CoreBundle/Entity/Entry.php | |||
@@ -109,6 +109,13 @@ class Entry | |||
109 | private $domainName; | 109 | private $domainName; |
110 | 110 | ||
111 | /** | 111 | /** |
112 | * @var string | ||
113 | * | ||
114 | * @ORM\Column(name="preview_picture", type="text", nullable=true) | ||
115 | */ | ||
116 | private $previewPicture; | ||
117 | |||
118 | /** | ||
112 | * @var bool | 119 | * @var bool |
113 | * | 120 | * |
114 | * @ORM\Column(name="is_public", type="boolean", nullable=true, options={"default" = false}) | 121 | * @ORM\Column(name="is_public", type="boolean", nullable=true, options={"default" = false}) |
@@ -419,4 +426,28 @@ class Entry | |||
419 | { | 426 | { |
420 | $this->tags->removeElement($tag); | 427 | $this->tags->removeElement($tag); |
421 | } | 428 | } |
429 | |||
430 | /** | ||
431 | * Set previewPicture. | ||
432 | * | ||
433 | * @param string $previewPicture | ||
434 | * | ||
435 | * @return Entry | ||
436 | */ | ||
437 | public function setPreviewPicture($previewPicture) | ||
438 | { | ||
439 | $this->previewPicture = $previewPicture; | ||
440 | |||
441 | return $this; | ||
442 | } | ||
443 | |||
444 | /** | ||
445 | * Get previewPicture. | ||
446 | * | ||
447 | * @return string | ||
448 | */ | ||
449 | public function getPreviewPicture() | ||
450 | { | ||
451 | return $this->previewPicture; | ||
452 | } | ||
422 | } | 453 | } |
diff --git a/src/Wallabag/CoreBundle/Helper/Content.php b/src/Wallabag/CoreBundle/Helper/Content.php deleted file mode 100644 index 1cc5e4cf..00000000 --- a/src/Wallabag/CoreBundle/Helper/Content.php +++ /dev/null | |||
@@ -1,34 +0,0 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Wallabag\CoreBundle\Helper; | ||
4 | |||
5 | class Content | ||
6 | { | ||
7 | private $title; | ||
8 | |||
9 | private $body; | ||
10 | |||
11 | public function __constructor() | ||
12 | { | ||
13 | } | ||
14 | |||
15 | public function getTitle() | ||
16 | { | ||
17 | return $this->title; | ||
18 | } | ||
19 | |||
20 | public function setTitle($title) | ||
21 | { | ||
22 | $this->title = $title; | ||
23 | } | ||
24 | |||
25 | public function getBody() | ||
26 | { | ||
27 | return $this->body; | ||
28 | } | ||
29 | |||
30 | public function setBody($body) | ||
31 | { | ||
32 | $this->body = $body; | ||
33 | } | ||
34 | } | ||
diff --git a/src/Wallabag/CoreBundle/Helper/ContentProxy.php b/src/Wallabag/CoreBundle/Helper/ContentProxy.php new file mode 100644 index 00000000..4565d8e7 --- /dev/null +++ b/src/Wallabag/CoreBundle/Helper/ContentProxy.php | |||
@@ -0,0 +1,60 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Wallabag\CoreBundle\Helper; | ||
4 | |||
5 | use Graby\Graby; | ||
6 | use Wallabag\CoreBundle\Entity\Entry; | ||
7 | |||
8 | /** | ||
9 | * This kind of proxy class take care of getting the content from an url | ||
10 | * and update the entry with what it found. | ||
11 | */ | ||
12 | class ContentProxy | ||
13 | { | ||
14 | protected $graby; | ||
15 | |||
16 | public function __construct(Graby $graby) | ||
17 | { | ||
18 | $this->graby = $graby; | ||
19 | } | ||
20 | |||
21 | /** | ||
22 | * Fetch content using graby and hydrate given entry with results information. | ||
23 | * In case we couldn't find content, we'll try to use Open Graph data. | ||
24 | * | ||
25 | * @param Entry $entry Entry to update | ||
26 | * @param string $url Url to grab content for | ||
27 | * | ||
28 | * @return Entry | ||
29 | */ | ||
30 | public function updateEntry(Entry $entry, $url) | ||
31 | { | ||
32 | $content = $this->graby->fetchContent($url); | ||
33 | |||
34 | $title = $content['title']; | ||
35 | if (!$title && isset($content['open_graph']['og_title'])) { | ||
36 | $title = $content['open_graph']['og_title']; | ||
37 | } | ||
38 | |||
39 | $html = $content['html']; | ||
40 | if (false === $html) { | ||
41 | $html = '<p>Unable to retrieve readable content.</p>'; | ||
42 | |||
43 | if (isset($content['open_graph']['og_description'])) { | ||
44 | $html .= '<p><i>But we found a short description: </i></p>'; | ||
45 | $html .= $content['open_graph']['og_description']; | ||
46 | } | ||
47 | } | ||
48 | |||
49 | $entry->setUrl($content['url'] ?: $url); | ||
50 | $entry->setTitle($title); | ||
51 | $entry->setContent($html); | ||
52 | $entry->setMimetype($content['content_type']); | ||
53 | |||
54 | if (isset($content['open_graph']['og_image'])) { | ||
55 | $entry->setPreviewPicture($content['open_graph']['og_image']); | ||
56 | } | ||
57 | |||
58 | return $entry; | ||
59 | } | ||
60 | } | ||
diff --git a/src/Wallabag/CoreBundle/Helper/Url.php b/src/Wallabag/CoreBundle/Helper/Url.php deleted file mode 100644 index 35eb260d..00000000 --- a/src/Wallabag/CoreBundle/Helper/Url.php +++ /dev/null | |||
@@ -1,28 +0,0 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Wallabag\CoreBundle\Helper; | ||
4 | |||
5 | class Url | ||
6 | { | ||
7 | public $url; | ||
8 | |||
9 | public function __construct($url) | ||
10 | { | ||
11 | $this->url = base64_decode($url); | ||
12 | } | ||
13 | |||
14 | public function getUrl() | ||
15 | { | ||
16 | return $this->url; | ||
17 | } | ||
18 | |||
19 | public function setUrl($url) | ||
20 | { | ||
21 | $this->url = $url; | ||
22 | } | ||
23 | |||
24 | public function isCorrect() | ||
25 | { | ||
26 | return filter_var($this->url, FILTER_VALIDATE_URL) !== false; | ||
27 | } | ||
28 | } | ||
diff --git a/src/Wallabag/CoreBundle/Resources/config/services.yml b/src/Wallabag/CoreBundle/Resources/config/services.yml index 08eae327..3beb5d0e 100644 --- a/src/Wallabag/CoreBundle/Resources/config/services.yml +++ b/src/Wallabag/CoreBundle/Resources/config/services.yml | |||
@@ -30,3 +30,13 @@ services: | |||
30 | wallabag_core.doctrine.prefixed_naming_strategy: | 30 | wallabag_core.doctrine.prefixed_naming_strategy: |
31 | class: Wallabag\CoreBundle\Doctrine\Mapping\PrefixedNamingStrategy | 31 | class: Wallabag\CoreBundle\Doctrine\Mapping\PrefixedNamingStrategy |
32 | arguments: [%database_table_prefix%] | 32 | arguments: [%database_table_prefix%] |
33 | |||
34 | wallabag_core.graby: | ||
35 | class: Graby\Graby | ||
36 | arguments: | ||
37 | - { error_message: false } | ||
38 | |||
39 | wallabag_core.content_proxy: | ||
40 | class: Wallabag\CoreBundle\Helper\ContentProxy | ||
41 | arguments: | ||
42 | - @wallabag_core.graby | ||
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/entry.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/entry.html.twig index 47ca661a..75ac2a6b 100644 --- a/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/entry.html.twig +++ b/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/entry.html.twig | |||
@@ -10,7 +10,7 @@ | |||
10 | <div class="nav-wrapper cyan darken-1"> | 10 | <div class="nav-wrapper cyan darken-1"> |
11 | <ul> | 11 | <ul> |
12 | <li> | 12 | <li> |
13 | <a class="waves-effect" href="/"> | 13 | <a class="waves-effect" href="{{ path('homepage') }}"> |
14 | <i class="mdi-action-exit-to-app"></i> | 14 | <i class="mdi-action-exit-to-app"></i> |
15 | </a> | 15 | </a> |
16 | </li> | 16 | </li> |
@@ -36,7 +36,7 @@ | |||
36 | </nav> | 36 | </nav> |
37 | <ul id="slide-out" class="collapsible side-nav fixed reader-mode" data-collapsible="accordion"> | 37 | <ul id="slide-out" class="collapsible side-nav fixed reader-mode" data-collapsible="accordion"> |
38 | <li class="bold border-bottom hide-on-med-and-down"> | 38 | <li class="bold border-bottom hide-on-med-and-down"> |
39 | <a class="waves-effect collapsible-header" href="/"> | 39 | <a class="waves-effect collapsible-header" href="{{ path('homepage') }}"> |
40 | <i class="mdi-action-exit-to-app small"></i> | 40 | <i class="mdi-action-exit-to-app small"></i> |
41 | <span>{% trans %}back{% endtrans %}</span> | 41 | <span>{% trans %}back{% endtrans %}</span> |
42 | </a> | 42 | </a> |
diff --git a/src/Wallabag/CoreBundle/Service/Extractor.php b/src/Wallabag/CoreBundle/Service/Extractor.php deleted file mode 100644 index 4c067d3a..00000000 --- a/src/Wallabag/CoreBundle/Service/Extractor.php +++ /dev/null | |||
@@ -1,96 +0,0 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Wallabag\CoreBundle\Service; | ||
4 | |||
5 | use Wallabag\CoreBundle\Helper\Content; | ||
6 | use Wallabag\CoreBundle\Helper\Url; | ||
7 | |||
8 | final class Extractor | ||
9 | { | ||
10 | public static function extract($url) | ||
11 | { | ||
12 | $pageContent = self::getPageContent(new Url(base64_encode($url))); | ||
13 | $title = $pageContent['rss']['channel']['item']['title'] ?: parse_url($url, PHP_URL_HOST); | ||
14 | $body = $pageContent['rss']['channel']['item']['description']; | ||
15 | |||
16 | $content = new Content(); | ||
17 | $content->setTitle($title); | ||
18 | $content->setBody($body); | ||
19 | |||
20 | return $content; | ||
21 | } | ||
22 | |||
23 | /** | ||
24 | * Get the content for a given URL (by a call to FullTextFeed). | ||
25 | * | ||
26 | * @param Url $url | ||
27 | * | ||
28 | * @return mixed | ||
29 | */ | ||
30 | public static function getPageContent(Url $url) | ||
31 | { | ||
32 | // Saving and clearing context | ||
33 | $REAL = array(); | ||
34 | foreach ($GLOBALS as $key => $value) { | ||
35 | if ($key != 'GLOBALS' && $key != '_SESSION' && $key != 'HTTP_SESSION_VARS') { | ||
36 | $GLOBALS[$key] = array(); | ||
37 | $REAL[$key] = $value; | ||
38 | } | ||
39 | } | ||
40 | // Saving and clearing session | ||
41 | if (isset($_SESSION)) { | ||
42 | $REAL_SESSION = array(); | ||
43 | foreach ($_SESSION as $key => $value) { | ||
44 | $REAL_SESSION[$key] = $value; | ||
45 | unset($_SESSION[$key]); | ||
46 | } | ||
47 | } | ||
48 | |||
49 | // Running code in different context | ||
50 | $scope = function () { | ||
51 | extract(func_get_arg(1)); | ||
52 | $_GET = $_REQUEST = array( | ||
53 | 'url' => $url->getUrl(), | ||
54 | 'max' => 5, | ||
55 | 'links' => 'preserve', | ||
56 | 'exc' => '', | ||
57 | 'format' => 'json', | ||
58 | 'submit' => 'Create Feed', | ||
59 | ); | ||
60 | ob_start(); | ||
61 | require func_get_arg(0); | ||
62 | $json = ob_get_contents(); | ||
63 | ob_end_clean(); | ||
64 | |||
65 | return $json; | ||
66 | }; | ||
67 | |||
68 | // Silence $scope function to avoid | ||
69 | // issues with FTRSS when error_reporting is to high | ||
70 | // FTRSS generates PHP warnings which break output | ||
71 | $json = @$scope(__DIR__.'/../../../../vendor/wallabag/Fivefilters_Libraries/makefulltextfeed.php', array('url' => $url)); | ||
72 | |||
73 | // Clearing and restoring context | ||
74 | foreach ($GLOBALS as $key => $value) { | ||
75 | if ($key != 'GLOBALS' && $key != '_SESSION') { | ||
76 | unset($GLOBALS[$key]); | ||
77 | } | ||
78 | } | ||
79 | foreach ($REAL as $key => $value) { | ||
80 | $GLOBALS[$key] = $value; | ||
81 | } | ||
82 | |||
83 | // Clearing and restoring session | ||
84 | if (isset($REAL_SESSION)) { | ||
85 | foreach ($_SESSION as $key => $value) { | ||
86 | unset($_SESSION[$key]); | ||
87 | } | ||
88 | |||
89 | foreach ($REAL_SESSION as $key => $value) { | ||
90 | $_SESSION[$key] = $value; | ||
91 | } | ||
92 | } | ||
93 | |||
94 | return json_decode($json, true); | ||
95 | } | ||
96 | } | ||
diff --git a/src/Wallabag/CoreBundle/Tests/Helper/ContentProxyTest.php b/src/Wallabag/CoreBundle/Tests/Helper/ContentProxyTest.php new file mode 100644 index 00000000..71a004ff --- /dev/null +++ b/src/Wallabag/CoreBundle/Tests/Helper/ContentProxyTest.php | |||
@@ -0,0 +1,84 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Wallabag\CoreBundle\Tests\Helper; | ||
4 | |||
5 | use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; | ||
6 | use Wallabag\CoreBundle\Entity\Entry; | ||
7 | use Wallabag\CoreBundle\Entity\User; | ||
8 | use Wallabag\CoreBundle\Helper\ContentProxy; | ||
9 | |||
10 | class ContentProxyTest extends KernelTestCase | ||
11 | { | ||
12 | public function testWithEmptyContent() | ||
13 | { | ||
14 | $graby = $this->getMockBuilder('Graby\Graby') | ||
15 | ->setMethods(array('fetchContent')) | ||
16 | ->disableOriginalConstructor() | ||
17 | ->getMock(); | ||
18 | |||
19 | $graby->expects($this->any()) | ||
20 | ->method('fetchContent') | ||
21 | ->willReturn(array('html' => false, 'title' => '', 'url' => '', 'content_type' => '')); | ||
22 | |||
23 | $proxy = new ContentProxy($graby); | ||
24 | $entry = $proxy->updateEntry(new Entry(new User()), 'http://0.0.0.0'); | ||
25 | |||
26 | $this->assertEquals('http://0.0.0.0', $entry->getUrl()); | ||
27 | $this->assertEmpty($entry->getTitle()); | ||
28 | $this->assertEquals('<p>Unable to retrieve readable content.</p>', $entry->getContent()); | ||
29 | $this->assertEmpty($entry->getPreviewPicture()); | ||
30 | $this->assertEmpty($entry->getMimetype()); | ||
31 | } | ||
32 | |||
33 | public function testWithEmptyContentButOG() | ||
34 | { | ||
35 | $graby = $this->getMockBuilder('Graby\Graby') | ||
36 | ->setMethods(array('fetchContent')) | ||
37 | ->disableOriginalConstructor() | ||
38 | ->getMock(); | ||
39 | |||
40 | $graby->expects($this->any()) | ||
41 | ->method('fetchContent') | ||
42 | ->willReturn(array('html' => false, 'title' => '', 'url' => '', 'content_type' => '', 'open_graph' => array('og_title' => 'my title', 'og_description' => 'desc'))); | ||
43 | |||
44 | $proxy = new ContentProxy($graby); | ||
45 | $entry = $proxy->updateEntry(new Entry(new User()), 'http://0.0.0.0'); | ||
46 | |||
47 | $this->assertEquals('http://0.0.0.0', $entry->getUrl()); | ||
48 | $this->assertEquals('my title', $entry->getTitle()); | ||
49 | $this->assertEquals('<p>Unable to retrieve readable content.</p><p><i>But we found a short description: </i></p>desc', $entry->getContent()); | ||
50 | $this->assertEmpty($entry->getPreviewPicture()); | ||
51 | $this->assertEmpty($entry->getMimetype()); | ||
52 | } | ||
53 | |||
54 | public function testWithContent() | ||
55 | { | ||
56 | $graby = $this->getMockBuilder('Graby\Graby') | ||
57 | ->setMethods(array('fetchContent')) | ||
58 | ->disableOriginalConstructor() | ||
59 | ->getMock(); | ||
60 | |||
61 | $graby->expects($this->any()) | ||
62 | ->method('fetchContent') | ||
63 | ->willReturn(array( | ||
64 | 'html' => 'this is my content', | ||
65 | 'title' => 'this is my title', | ||
66 | 'url' => 'http://1.1.1.1', | ||
67 | 'content_type' => 'text/html', | ||
68 | 'open_graph' => array( | ||
69 | 'og_title' => 'my OG title', | ||
70 | 'og_description' => 'OG desc', | ||
71 | 'og_image' => 'http://3.3.3.3/cover.jpg', | ||
72 | ), | ||
73 | )); | ||
74 | |||
75 | $proxy = new ContentProxy($graby); | ||
76 | $entry = $proxy->updateEntry(new Entry(new User()), 'http://0.0.0.0'); | ||
77 | |||
78 | $this->assertEquals('http://1.1.1.1', $entry->getUrl()); | ||
79 | $this->assertEquals('this is my title', $entry->getTitle()); | ||
80 | $this->assertEquals('this is my content', $entry->getContent()); | ||
81 | $this->assertEquals('http://3.3.3.3/cover.jpg', $entry->getPreviewPicture()); | ||
82 | $this->assertEquals('text/html', $entry->getMimetype()); | ||
83 | } | ||
84 | } | ||