diff options
author | Nicolas Lœuillet <nicolas@loeuillet.org> | 2015-11-09 16:45:48 +0100 |
---|---|---|
committer | Nicolas Lœuillet <nicolas@loeuillet.org> | 2015-11-09 16:45:48 +0100 |
commit | 0a0c600887dde4cc755de0862a3301830c415882 (patch) | |
tree | 4d82bc16e921248bb6ab1b203a33e1b55b4ff445 /src/Wallabag | |
parent | f1eccfd63f214dcc730ab0d18a694a5465f425db (diff) | |
parent | 16bbb4aa417188e7c21eb4a1734adf0f0c9b25f9 (diff) | |
download | wallabag-0a0c600887dde4cc755de0862a3301830c415882.tar.gz wallabag-0a0c600887dde4cc755de0862a3301830c415882.tar.zst wallabag-0a0c600887dde4cc755de0862a3301830c415882.zip |
Merge pull request #1422 from wallabag/v2-ebook
V2 – Export entries
Diffstat (limited to 'src/Wallabag')
10 files changed, 775 insertions, 4 deletions
diff --git a/src/Wallabag/CoreBundle/Controller/ExportController.php b/src/Wallabag/CoreBundle/Controller/ExportController.php new file mode 100644 index 00000000..c8ef49a2 --- /dev/null +++ b/src/Wallabag/CoreBundle/Controller/ExportController.php | |||
@@ -0,0 +1,65 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Wallabag\CoreBundle\Controller; | ||
4 | |||
5 | use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; | ||
6 | use Symfony\Bundle\FrameworkBundle\Controller\Controller; | ||
7 | use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; | ||
8 | use Wallabag\CoreBundle\Entity\Entry; | ||
9 | |||
10 | /** | ||
11 | * The try/catch can be removed once all formats will be implemented. | ||
12 | * Still need implementation: txt. | ||
13 | */ | ||
14 | class ExportController extends Controller | ||
15 | { | ||
16 | /** | ||
17 | * Gets one entry content. | ||
18 | * | ||
19 | * @param Entry $entry | ||
20 | * | ||
21 | * @Route("/export/{id}.{format}", name="export_entry", requirements={ | ||
22 | * "format": "epub|mobi|pdf|json|xml|txt|csv", | ||
23 | * "id": "\d+" | ||
24 | * }) | ||
25 | */ | ||
26 | public function downloadEntryAction(Entry $entry, $format) | ||
27 | { | ||
28 | try { | ||
29 | return $this->get('wallabag_core.helper.entries_export') | ||
30 | ->setEntries($entry) | ||
31 | ->updateTitle('entry') | ||
32 | ->exportAs($format); | ||
33 | } catch (\InvalidArgumentException $e) { | ||
34 | throw new NotFoundHttpException($e->getMessage()); | ||
35 | } | ||
36 | } | ||
37 | |||
38 | /** | ||
39 | * Export all entries for current user. | ||
40 | * | ||
41 | * @Route("/export/{category}.{format}", name="export_entries", requirements={ | ||
42 | * "format": "epub|mobi|pdf|json|xml|txt|csv", | ||
43 | * "category": "all|unread|starred|archive" | ||
44 | * }) | ||
45 | */ | ||
46 | public function downloadEntriesAction($format, $category) | ||
47 | { | ||
48 | $method = ucfirst($category); | ||
49 | $methodBuilder = 'getBuilderFor'.$method.'ByUser'; | ||
50 | $entries = $this->getDoctrine() | ||
51 | ->getRepository('WallabagCoreBundle:Entry') | ||
52 | ->$methodBuilder($this->getUser()->getId()) | ||
53 | ->getQuery() | ||
54 | ->getResult(); | ||
55 | |||
56 | try { | ||
57 | return $this->get('wallabag_core.helper.entries_export') | ||
58 | ->setEntries($entries) | ||
59 | ->updateTitle($method) | ||
60 | ->exportAs($format); | ||
61 | } catch (\InvalidArgumentException $e) { | ||
62 | throw new NotFoundHttpException($e->getMessage()); | ||
63 | } | ||
64 | } | ||
65 | } | ||
diff --git a/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadEntryData.php b/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadEntryData.php index 7e64c5e1..176c529e 100644 --- a/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadEntryData.php +++ b/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadEntryData.php | |||
@@ -19,6 +19,7 @@ class LoadEntryData extends AbstractFixture implements OrderedFixtureInterface | |||
19 | $entry1->setUrl('http://0.0.0.0'); | 19 | $entry1->setUrl('http://0.0.0.0'); |
20 | $entry1->setReadingTime(11); | 20 | $entry1->setReadingTime(11); |
21 | $entry1->setDomainName('domain.io'); | 21 | $entry1->setDomainName('domain.io'); |
22 | $entry1->setMimetype('text/html'); | ||
22 | $entry1->setTitle('test title entry1'); | 23 | $entry1->setTitle('test title entry1'); |
23 | $entry1->setContent('This is my content /o/'); | 24 | $entry1->setContent('This is my content /o/'); |
24 | $entry1->setLanguage('en'); | 25 | $entry1->setLanguage('en'); |
@@ -31,6 +32,7 @@ class LoadEntryData extends AbstractFixture implements OrderedFixtureInterface | |||
31 | $entry2->setUrl('http://0.0.0.0'); | 32 | $entry2->setUrl('http://0.0.0.0'); |
32 | $entry2->setReadingTime(1); | 33 | $entry2->setReadingTime(1); |
33 | $entry2->setDomainName('domain.io'); | 34 | $entry2->setDomainName('domain.io'); |
35 | $entry2->setMimetype('text/html'); | ||
34 | $entry2->setTitle('test title entry2'); | 36 | $entry2->setTitle('test title entry2'); |
35 | $entry2->setContent('This is my content /o/'); | 37 | $entry2->setContent('This is my content /o/'); |
36 | $entry2->setLanguage('fr'); | 38 | $entry2->setLanguage('fr'); |
@@ -43,6 +45,7 @@ class LoadEntryData extends AbstractFixture implements OrderedFixtureInterface | |||
43 | $entry3->setUrl('http://0.0.0.0'); | 45 | $entry3->setUrl('http://0.0.0.0'); |
44 | $entry3->setReadingTime(1); | 46 | $entry3->setReadingTime(1); |
45 | $entry3->setDomainName('domain.io'); | 47 | $entry3->setDomainName('domain.io'); |
48 | $entry3->setMimetype('text/html'); | ||
46 | $entry3->setTitle('test title entry3'); | 49 | $entry3->setTitle('test title entry3'); |
47 | $entry3->setContent('This is my content /o/'); | 50 | $entry3->setContent('This is my content /o/'); |
48 | $entry3->setLanguage('en'); | 51 | $entry3->setLanguage('en'); |
@@ -63,6 +66,7 @@ class LoadEntryData extends AbstractFixture implements OrderedFixtureInterface | |||
63 | $entry4->setUrl('http://0.0.0.0'); | 66 | $entry4->setUrl('http://0.0.0.0'); |
64 | $entry4->setReadingTime(12); | 67 | $entry4->setReadingTime(12); |
65 | $entry4->setDomainName('domain.io'); | 68 | $entry4->setDomainName('domain.io'); |
69 | $entry4->setMimetype('text/html'); | ||
66 | $entry4->setTitle('test title entry4'); | 70 | $entry4->setTitle('test title entry4'); |
67 | $entry4->setContent('This is my content /o/'); | 71 | $entry4->setContent('This is my content /o/'); |
68 | $entry4->setLanguage('en'); | 72 | $entry4->setLanguage('en'); |
@@ -83,6 +87,7 @@ class LoadEntryData extends AbstractFixture implements OrderedFixtureInterface | |||
83 | $entry5->setUrl('http://0.0.0.0'); | 87 | $entry5->setUrl('http://0.0.0.0'); |
84 | $entry5->setReadingTime(12); | 88 | $entry5->setReadingTime(12); |
85 | $entry5->setDomainName('domain.io'); | 89 | $entry5->setDomainName('domain.io'); |
90 | $entry5->setMimetype('text/html'); | ||
86 | $entry5->setTitle('test title entry5'); | 91 | $entry5->setTitle('test title entry5'); |
87 | $entry5->setContent('This is my content /o/'); | 92 | $entry5->setContent('This is my content /o/'); |
88 | $entry5->setStarred(true); | 93 | $entry5->setStarred(true); |
@@ -97,6 +102,7 @@ class LoadEntryData extends AbstractFixture implements OrderedFixtureInterface | |||
97 | $entry6->setUrl('http://0.0.0.0'); | 102 | $entry6->setUrl('http://0.0.0.0'); |
98 | $entry6->setReadingTime(12); | 103 | $entry6->setReadingTime(12); |
99 | $entry6->setDomainName('domain.io'); | 104 | $entry6->setDomainName('domain.io'); |
105 | $entry6->setMimetype('text/html'); | ||
100 | $entry6->setTitle('test title entry6'); | 106 | $entry6->setTitle('test title entry6'); |
101 | $entry6->setContent('This is my content /o/'); | 107 | $entry6->setContent('This is my content /o/'); |
102 | $entry6->setArchived(true); | 108 | $entry6->setArchived(true); |
diff --git a/src/Wallabag/CoreBundle/Entity/Entry.php b/src/Wallabag/CoreBundle/Entity/Entry.php index 9e5446a6..5aa582f8 100644 --- a/src/Wallabag/CoreBundle/Entity/Entry.php +++ b/src/Wallabag/CoreBundle/Entity/Entry.php | |||
@@ -6,6 +6,7 @@ use Doctrine\Common\Collections\ArrayCollection; | |||
6 | use Doctrine\ORM\Mapping as ORM; | 6 | use Doctrine\ORM\Mapping as ORM; |
7 | use Symfony\Component\Validator\Constraints as Assert; | 7 | use Symfony\Component\Validator\Constraints as Assert; |
8 | use Hateoas\Configuration\Annotation as Hateoas; | 8 | use Hateoas\Configuration\Annotation as Hateoas; |
9 | use JMS\Serializer\Annotation\Groups; | ||
9 | use JMS\Serializer\Annotation\XmlRoot; | 10 | use JMS\Serializer\Annotation\XmlRoot; |
10 | use Wallabag\UserBundle\Entity\User; | 11 | use Wallabag\UserBundle\Entity\User; |
11 | 12 | ||
@@ -27,6 +28,8 @@ class Entry | |||
27 | * @ORM\Column(name="id", type="integer") | 28 | * @ORM\Column(name="id", type="integer") |
28 | * @ORM\Id | 29 | * @ORM\Id |
29 | * @ORM\GeneratedValue(strategy="AUTO") | 30 | * @ORM\GeneratedValue(strategy="AUTO") |
31 | * | ||
32 | * @Groups({"entries_for_user", "export_all"}) | ||
30 | */ | 33 | */ |
31 | private $id; | 34 | private $id; |
32 | 35 | ||
@@ -34,6 +37,8 @@ class Entry | |||
34 | * @var string | 37 | * @var string |
35 | * | 38 | * |
36 | * @ORM\Column(name="title", type="text", nullable=true) | 39 | * @ORM\Column(name="title", type="text", nullable=true) |
40 | * | ||
41 | * @Groups({"entries_for_user", "export_all"}) | ||
37 | */ | 42 | */ |
38 | private $title; | 43 | private $title; |
39 | 44 | ||
@@ -42,6 +47,8 @@ class Entry | |||
42 | * | 47 | * |
43 | * @Assert\NotBlank() | 48 | * @Assert\NotBlank() |
44 | * @ORM\Column(name="url", type="text", nullable=true) | 49 | * @ORM\Column(name="url", type="text", nullable=true) |
50 | * | ||
51 | * @Groups({"entries_for_user", "export_all"}) | ||
45 | */ | 52 | */ |
46 | private $url; | 53 | private $url; |
47 | 54 | ||
@@ -49,6 +56,8 @@ class Entry | |||
49 | * @var bool | 56 | * @var bool |
50 | * | 57 | * |
51 | * @ORM\Column(name="is_archived", type="boolean") | 58 | * @ORM\Column(name="is_archived", type="boolean") |
59 | * | ||
60 | * @Groups({"entries_for_user", "export_all"}) | ||
52 | */ | 61 | */ |
53 | private $isArchived = false; | 62 | private $isArchived = false; |
54 | 63 | ||
@@ -56,6 +65,8 @@ class Entry | |||
56 | * @var bool | 65 | * @var bool |
57 | * | 66 | * |
58 | * @ORM\Column(name="is_starred", type="boolean") | 67 | * @ORM\Column(name="is_starred", type="boolean") |
68 | * | ||
69 | * @Groups({"entries_for_user", "export_all"}) | ||
59 | */ | 70 | */ |
60 | private $isStarred = false; | 71 | private $isStarred = false; |
61 | 72 | ||
@@ -63,6 +74,8 @@ class Entry | |||
63 | * @var string | 74 | * @var string |
64 | * | 75 | * |
65 | * @ORM\Column(name="content", type="text", nullable=true) | 76 | * @ORM\Column(name="content", type="text", nullable=true) |
77 | * | ||
78 | * @Groups({"entries_for_user", "export_all"}) | ||
66 | */ | 79 | */ |
67 | private $content; | 80 | private $content; |
68 | 81 | ||
@@ -70,6 +83,8 @@ class Entry | |||
70 | * @var date | 83 | * @var date |
71 | * | 84 | * |
72 | * @ORM\Column(name="created_at", type="datetime") | 85 | * @ORM\Column(name="created_at", type="datetime") |
86 | * | ||
87 | * @Groups({"export_all"}) | ||
73 | */ | 88 | */ |
74 | private $createdAt; | 89 | private $createdAt; |
75 | 90 | ||
@@ -77,6 +92,8 @@ class Entry | |||
77 | * @var date | 92 | * @var date |
78 | * | 93 | * |
79 | * @ORM\Column(name="updated_at", type="datetime") | 94 | * @ORM\Column(name="updated_at", type="datetime") |
95 | * | ||
96 | * @Groups({"export_all"}) | ||
80 | */ | 97 | */ |
81 | private $updatedAt; | 98 | private $updatedAt; |
82 | 99 | ||
@@ -84,6 +101,8 @@ class Entry | |||
84 | * @var string | 101 | * @var string |
85 | * | 102 | * |
86 | * @ORM\Column(name="comments", type="text", nullable=true) | 103 | * @ORM\Column(name="comments", type="text", nullable=true) |
104 | * | ||
105 | * @Groups({"export_all"}) | ||
87 | */ | 106 | */ |
88 | private $comments; | 107 | private $comments; |
89 | 108 | ||
@@ -91,6 +110,8 @@ class Entry | |||
91 | * @var string | 110 | * @var string |
92 | * | 111 | * |
93 | * @ORM\Column(name="mimetype", type="text", nullable=true) | 112 | * @ORM\Column(name="mimetype", type="text", nullable=true) |
113 | * | ||
114 | * @Groups({"entries_for_user", "export_all"}) | ||
94 | */ | 115 | */ |
95 | private $mimetype; | 116 | private $mimetype; |
96 | 117 | ||
@@ -98,6 +119,8 @@ class Entry | |||
98 | * @var string | 119 | * @var string |
99 | * | 120 | * |
100 | * @ORM\Column(name="language", type="text", nullable=true) | 121 | * @ORM\Column(name="language", type="text", nullable=true) |
122 | * | ||
123 | * @Groups({"entries_for_user", "export_all"}) | ||
101 | */ | 124 | */ |
102 | private $language; | 125 | private $language; |
103 | 126 | ||
@@ -105,6 +128,8 @@ class Entry | |||
105 | * @var int | 128 | * @var int |
106 | * | 129 | * |
107 | * @ORM\Column(name="reading_time", type="integer", nullable=true) | 130 | * @ORM\Column(name="reading_time", type="integer", nullable=true) |
131 | * | ||
132 | * @Groups({"entries_for_user", "export_all"}) | ||
108 | */ | 133 | */ |
109 | private $readingTime; | 134 | private $readingTime; |
110 | 135 | ||
@@ -112,6 +137,8 @@ class Entry | |||
112 | * @var string | 137 | * @var string |
113 | * | 138 | * |
114 | * @ORM\Column(name="domain_name", type="text", nullable=true) | 139 | * @ORM\Column(name="domain_name", type="text", nullable=true) |
140 | * | ||
141 | * @Groups({"entries_for_user", "export_all"}) | ||
115 | */ | 142 | */ |
116 | private $domainName; | 143 | private $domainName; |
117 | 144 | ||
@@ -119,6 +146,8 @@ class Entry | |||
119 | * @var string | 146 | * @var string |
120 | * | 147 | * |
121 | * @ORM\Column(name="preview_picture", type="text", nullable=true) | 148 | * @ORM\Column(name="preview_picture", type="text", nullable=true) |
149 | * | ||
150 | * @Groups({"entries_for_user", "export_all"}) | ||
122 | */ | 151 | */ |
123 | private $previewPicture; | 152 | private $previewPicture; |
124 | 153 | ||
@@ -126,17 +155,23 @@ class Entry | |||
126 | * @var bool | 155 | * @var bool |
127 | * | 156 | * |
128 | * @ORM\Column(name="is_public", type="boolean", nullable=true, options={"default" = false}) | 157 | * @ORM\Column(name="is_public", type="boolean", nullable=true, options={"default" = false}) |
158 | * | ||
159 | * @Groups({"export_all"}) | ||
129 | */ | 160 | */ |
130 | private $isPublic; | 161 | private $isPublic; |
131 | 162 | ||
132 | /** | 163 | /** |
133 | * @ORM\ManyToOne(targetEntity="Wallabag\UserBundle\Entity\User", inversedBy="entries") | 164 | * @ORM\ManyToOne(targetEntity="Wallabag\UserBundle\Entity\User", inversedBy="entries") |
165 | * | ||
166 | * @Groups({"export_all"}) | ||
134 | */ | 167 | */ |
135 | private $user; | 168 | private $user; |
136 | 169 | ||
137 | /** | 170 | /** |
138 | * @ORM\ManyToMany(targetEntity="Tag", inversedBy="entries", cascade={"persist"}) | 171 | * @ORM\ManyToMany(targetEntity="Tag", inversedBy="entries", cascade={"persist"}) |
139 | * @ORM\JoinTable | 172 | * @ORM\JoinTable |
173 | * | ||
174 | * @Groups({"entries_for_user", "export_all"}) | ||
140 | */ | 175 | */ |
141 | private $tags; | 176 | private $tags; |
142 | 177 | ||
diff --git a/src/Wallabag/CoreBundle/Helper/EntriesExport.php b/src/Wallabag/CoreBundle/Helper/EntriesExport.php new file mode 100644 index 00000000..d6a4d094 --- /dev/null +++ b/src/Wallabag/CoreBundle/Helper/EntriesExport.php | |||
@@ -0,0 +1,394 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Wallabag\CoreBundle\Helper; | ||
4 | |||
5 | use PHPePub\Core\EPub; | ||
6 | use PHPePub\Core\Structure\OPF\DublinCore; | ||
7 | use Symfony\Component\HttpFoundation\Response; | ||
8 | use JMS\Serializer; | ||
9 | use JMS\Serializer\SerializerBuilder; | ||
10 | use JMS\Serializer\SerializationContext; | ||
11 | |||
12 | /** | ||
13 | * This class doesn't have unit test BUT it's fully covered by a functional test with ExportControllerTest. | ||
14 | */ | ||
15 | class EntriesExport | ||
16 | { | ||
17 | private $wallabagUrl; | ||
18 | private $logoPath; | ||
19 | private $title = ''; | ||
20 | private $entries = array(); | ||
21 | private $authors = array('wallabag'); | ||
22 | private $language = ''; | ||
23 | private $tags = array(); | ||
24 | private $footerTemplate = '<div style="text-align:center;"> | ||
25 | <p>Produced by wallabag with %EXPORT_METHOD%</p> | ||
26 | <p>Please open <a href="https://github.com/wallabag/wallabag/issues">an issue</a> if you have trouble with the display of this E-Book on your device.</p> | ||
27 | </div'; | ||
28 | |||
29 | /** | ||
30 | * @param string $wallabagUrl Wallabag instance url | ||
31 | * @param string $logoPath Path to the logo FROM THE BUNDLE SCOPE | ||
32 | */ | ||
33 | public function __construct($wallabagUrl, $logoPath) | ||
34 | { | ||
35 | $this->wallabagUrl = $wallabagUrl; | ||
36 | $this->logoPath = $logoPath; | ||
37 | } | ||
38 | |||
39 | /** | ||
40 | * Define entries. | ||
41 | * | ||
42 | * @param array|Entry $entries An array of entries or one entry | ||
43 | */ | ||
44 | public function setEntries($entries) | ||
45 | { | ||
46 | if (!is_array($entries)) { | ||
47 | $this->language = $entries->getLanguage(); | ||
48 | $entries = array($entries); | ||
49 | } | ||
50 | |||
51 | $this->entries = $entries; | ||
52 | |||
53 | foreach ($entries as $entry) { | ||
54 | $this->tags[] = $entry->getTags(); | ||
55 | } | ||
56 | |||
57 | return $this; | ||
58 | } | ||
59 | |||
60 | /** | ||
61 | * Sets the category of which we want to get articles, or just one entry. | ||
62 | * | ||
63 | * @param string $method Method to get articles | ||
64 | */ | ||
65 | public function updateTitle($method) | ||
66 | { | ||
67 | $this->title = $method.' articles'; | ||
68 | |||
69 | if ('entry' === $method) { | ||
70 | $this->title = $this->entries[0]->getTitle(); | ||
71 | } | ||
72 | |||
73 | return $this; | ||
74 | } | ||
75 | |||
76 | /** | ||
77 | * Sets the output format. | ||
78 | * | ||
79 | * @param string $format | ||
80 | */ | ||
81 | public function exportAs($format) | ||
82 | { | ||
83 | switch ($format) { | ||
84 | case 'epub': | ||
85 | return $this->produceEpub(); | ||
86 | |||
87 | case 'mobi': | ||
88 | return $this->produceMobi(); | ||
89 | |||
90 | case 'pdf': | ||
91 | return $this->producePDF(); | ||
92 | |||
93 | case 'csv': | ||
94 | return $this->produceCSV(); | ||
95 | |||
96 | case 'json': | ||
97 | return $this->produceJSON(); | ||
98 | |||
99 | case 'xml': | ||
100 | return $this->produceXML(); | ||
101 | } | ||
102 | |||
103 | throw new \InvalidArgumentException(sprintf('The format "%s" is not yet supported.', $format)); | ||
104 | } | ||
105 | |||
106 | /** | ||
107 | * Use PHPePub to dump a .epub file. | ||
108 | */ | ||
109 | private function produceEpub() | ||
110 | { | ||
111 | /* | ||
112 | * Start and End of the book | ||
113 | */ | ||
114 | $content_start = | ||
115 | "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" | ||
116 | ."<html xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:epub=\"http://www.idpf.org/2007/ops\">\n" | ||
117 | .'<head>' | ||
118 | ."<meta http-equiv=\"Default-Style\" content=\"text/html; charset=utf-8\" />\n" | ||
119 | ."<title>wallabag articles book</title>\n" | ||
120 | ."</head>\n" | ||
121 | ."<body>\n"; | ||
122 | |||
123 | $bookEnd = "</body>\n</html>\n"; | ||
124 | |||
125 | $book = new EPub(EPub::BOOK_VERSION_EPUB3); | ||
126 | |||
127 | /* | ||
128 | * Book metadata | ||
129 | */ | ||
130 | |||
131 | $book->setTitle($this->title); | ||
132 | // Could also be the ISBN number, prefered for published books, or a UUID. | ||
133 | $book->setIdentifier($this->title, EPub::IDENTIFIER_URI); | ||
134 | // Not needed, but included for the example, Language is mandatory, but EPub defaults to "en". Use RFC3066 Language codes, such as "en", "da", "fr" etc. | ||
135 | $book->setLanguage($this->language); | ||
136 | $book->setDescription('Some articles saved on my wallabag'); | ||
137 | |||
138 | foreach ($this->authors as $author) { | ||
139 | $book->setAuthor($author, $author); | ||
140 | } | ||
141 | |||
142 | // I hope this is a non existant address :) | ||
143 | $book->setPublisher('wallabag', 'wallabag'); | ||
144 | // Strictly not needed as the book date defaults to time(). | ||
145 | $book->setDate(time()); | ||
146 | $book->setSourceURL($this->wallabagUrl); | ||
147 | |||
148 | $book->addDublinCoreMetadata(DublinCore::CONTRIBUTOR, 'PHP'); | ||
149 | $book->addDublinCoreMetadata(DublinCore::CONTRIBUTOR, 'wallabag'); | ||
150 | |||
151 | /* | ||
152 | * Front page | ||
153 | */ | ||
154 | if (file_exists($this->logoPath)) { | ||
155 | $book->setCoverImage('Cover.png', file_get_contents($this->logoPath), 'image/png'); | ||
156 | } | ||
157 | |||
158 | $book->addChapter('Notices', 'Cover2.html', $content_start.$this->getExportInformation('PHPePub').$bookEnd); | ||
159 | |||
160 | $book->buildTOC(); | ||
161 | |||
162 | /* | ||
163 | * Adding actual entries | ||
164 | */ | ||
165 | |||
166 | // set tags as subjects | ||
167 | foreach ($this->entries as $entry) { | ||
168 | foreach ($this->tags as $tag) { | ||
169 | $book->setSubject($tag['value']); | ||
170 | } | ||
171 | |||
172 | $chapter = $content_start.$entry->getContent().$bookEnd; | ||
173 | $book->addChapter($entry->getTitle(), htmlspecialchars($entry->getTitle()).'.html', $chapter, true, EPub::EXTERNAL_REF_ADD); | ||
174 | } | ||
175 | |||
176 | return Response::create( | ||
177 | $book->getBook(), | ||
178 | 200, | ||
179 | array( | ||
180 | 'Content-Description' => 'File Transfer', | ||
181 | 'Content-type' => 'application/epub+zip', | ||
182 | 'Content-Disposition' => 'attachment; filename="'.$this->title.'.epub"', | ||
183 | 'Content-Transfer-Encoding' => 'binary', | ||
184 | ) | ||
185 | )->send(); | ||
186 | } | ||
187 | |||
188 | /** | ||
189 | * Use PHPMobi to dump a .mobi file. | ||
190 | */ | ||
191 | private function produceMobi() | ||
192 | { | ||
193 | $mobi = new \MOBI(); | ||
194 | $content = new \MOBIFile(); | ||
195 | |||
196 | /* | ||
197 | * Book metadata | ||
198 | */ | ||
199 | $content->set('title', $this->title); | ||
200 | $content->set('author', implode($this->authors)); | ||
201 | $content->set('subject', $this->title); | ||
202 | |||
203 | /* | ||
204 | * Front page | ||
205 | */ | ||
206 | $content->appendParagraph($this->getExportInformation('PHPMobi')); | ||
207 | if (file_exists($this->logoPath)) { | ||
208 | $content->appendImage(imagecreatefrompng($this->logoPath)); | ||
209 | } | ||
210 | $content->appendPageBreak(); | ||
211 | |||
212 | /* | ||
213 | * Adding actual entries | ||
214 | */ | ||
215 | foreach ($this->entries as $entry) { | ||
216 | $content->appendChapterTitle($entry->getTitle()); | ||
217 | $content->appendParagraph($entry->getContent()); | ||
218 | $content->appendPageBreak(); | ||
219 | } | ||
220 | $mobi->setContentProvider($content); | ||
221 | |||
222 | // the browser inside Kindle Devices doesn't likes special caracters either, we limit to A-z/0-9 | ||
223 | $this->title = preg_replace('/[^A-Za-z0-9\-]/', '', $this->title); | ||
224 | |||
225 | return Response::create( | ||
226 | $mobi->toString(), | ||
227 | 200, | ||
228 | array( | ||
229 | 'Accept-Ranges' => 'bytes', | ||
230 | 'Content-Description' => 'File Transfer', | ||
231 | 'Content-type' => 'application/x-mobipocket-ebook', | ||
232 | 'Content-Disposition' => 'attachment; filename="'.$this->title.'.mobi"', | ||
233 | 'Content-Transfer-Encoding' => 'binary', | ||
234 | ) | ||
235 | )->send(); | ||
236 | } | ||
237 | |||
238 | /** | ||
239 | * Use TCPDF to dump a .pdf file. | ||
240 | */ | ||
241 | private function producePDF() | ||
242 | { | ||
243 | $pdf = new \TCPDF(PDF_PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT, true, 'UTF-8', false); | ||
244 | |||
245 | /* | ||
246 | * Book metadata | ||
247 | */ | ||
248 | $pdf->SetCreator(PDF_CREATOR); | ||
249 | $pdf->SetAuthor('wallabag'); | ||
250 | $pdf->SetTitle($this->title); | ||
251 | $pdf->SetSubject('Articles via wallabag'); | ||
252 | $pdf->SetKeywords('wallabag'); | ||
253 | |||
254 | /* | ||
255 | * Front page | ||
256 | */ | ||
257 | $pdf->AddPage(); | ||
258 | $intro = '<h1>'.$this->title.'</h1>'.$this->getExportInformation('tcpdf'); | ||
259 | |||
260 | $pdf->writeHTMLCell(0, 0, '', '', $intro, 0, 1, 0, true, '', true); | ||
261 | |||
262 | /* | ||
263 | * Adding actual entries | ||
264 | */ | ||
265 | foreach ($this->entries as $entry) { | ||
266 | foreach ($this->tags as $tag) { | ||
267 | $pdf->SetKeywords($tag['value']); | ||
268 | } | ||
269 | |||
270 | $pdf->AddPage(); | ||
271 | $html = '<h1>'.$entry->getTitle().'</h1>'; | ||
272 | $html .= $entry->getContent(); | ||
273 | |||
274 | $pdf->writeHTMLCell(0, 0, '', '', $html, 0, 1, 0, true, '', true); | ||
275 | } | ||
276 | |||
277 | // set image scale factor | ||
278 | $pdf->setImageScale(PDF_IMAGE_SCALE_RATIO); | ||
279 | |||
280 | return Response::create( | ||
281 | $pdf->Output('', 'S'), | ||
282 | 200, | ||
283 | array( | ||
284 | 'Content-Description' => 'File Transfer', | ||
285 | 'Content-type' => 'application/pdf', | ||
286 | 'Content-Disposition' => 'attachment; filename="'.$this->title.'.pdf"', | ||
287 | 'Content-Transfer-Encoding' => 'binary', | ||
288 | ) | ||
289 | )->send(); | ||
290 | } | ||
291 | |||
292 | /** | ||
293 | * Inspired from CsvFileDumper. | ||
294 | */ | ||
295 | private function produceCSV() | ||
296 | { | ||
297 | $delimiter = ';'; | ||
298 | $enclosure = '"'; | ||
299 | $handle = fopen('php://memory', 'rb+'); | ||
300 | |||
301 | fputcsv($handle, array('Title', 'URL', 'Content', 'Tags', 'MIME Type', 'Language'), $delimiter, $enclosure); | ||
302 | |||
303 | foreach ($this->entries as $entry) { | ||
304 | fputcsv( | ||
305 | $handle, | ||
306 | array( | ||
307 | $entry->getTitle(), | ||
308 | $entry->getURL(), | ||
309 | // remove new line to avoid crazy results | ||
310 | str_replace(array("\r\n", "\r", "\n"), '', $entry->getContent()), | ||
311 | implode(', ', $entry->getTags()->toArray()), | ||
312 | $entry->getMimetype(), | ||
313 | $entry->getLanguage(), | ||
314 | ), | ||
315 | $delimiter, | ||
316 | $enclosure | ||
317 | ); | ||
318 | } | ||
319 | |||
320 | rewind($handle); | ||
321 | $output = stream_get_contents($handle); | ||
322 | fclose($handle); | ||
323 | |||
324 | return Response::create( | ||
325 | $output, | ||
326 | 200, | ||
327 | array( | ||
328 | 'Content-type' => 'application/csv', | ||
329 | 'Content-Disposition' => 'attachment; filename="'.$this->title.'.csv"', | ||
330 | 'Content-Transfer-Encoding' => 'UTF-8', | ||
331 | ) | ||
332 | )->send(); | ||
333 | } | ||
334 | |||
335 | private function produceJSON() | ||
336 | { | ||
337 | return Response::create( | ||
338 | $this->prepareSerializingContent('json'), | ||
339 | 200, | ||
340 | array( | ||
341 | 'Content-type' => 'application/json', | ||
342 | 'Content-Disposition' => 'attachment; filename="'.$this->title.'.json"', | ||
343 | 'Content-Transfer-Encoding' => 'UTF-8', | ||
344 | ) | ||
345 | )->send(); | ||
346 | } | ||
347 | |||
348 | private function produceXML() | ||
349 | { | ||
350 | return Response::create( | ||
351 | $this->prepareSerializingContent('xml'), | ||
352 | 200, | ||
353 | array( | ||
354 | 'Content-type' => 'application/xml', | ||
355 | 'Content-Disposition' => 'attachment; filename="'.$this->title.'.xml"', | ||
356 | 'Content-Transfer-Encoding' => 'UTF-8', | ||
357 | ) | ||
358 | )->send(); | ||
359 | } | ||
360 | |||
361 | /** | ||
362 | * Return a Serializer object for producing processes that need it (JSON & XML). | ||
363 | * | ||
364 | * @return Serializer | ||
365 | */ | ||
366 | private function prepareSerializingContent($format) | ||
367 | { | ||
368 | $serializer = SerializerBuilder::create()->build(); | ||
369 | |||
370 | return $serializer->serialize( | ||
371 | $this->entries, | ||
372 | $format, | ||
373 | SerializationContext::create()->setGroups(array('entries_for_user')) | ||
374 | ); | ||
375 | } | ||
376 | |||
377 | /** | ||
378 | * Return a kind of footer / information for the epub. | ||
379 | * | ||
380 | * @param string $type Generator of the export, can be: tdpdf, PHPePub, PHPMobi | ||
381 | * | ||
382 | * @return string | ||
383 | */ | ||
384 | private function getExportInformation($type) | ||
385 | { | ||
386 | $info = str_replace('%EXPORT_METHOD%', $type, $this->footerTemplate); | ||
387 | |||
388 | if ('tcpdf' === $type) { | ||
389 | return str_replace('%IMAGE%', '<img src="'.$this->logoPath.'" />', $info); | ||
390 | } | ||
391 | |||
392 | return str_replace('%IMAGE%', '', $info); | ||
393 | } | ||
394 | } | ||
diff --git a/src/Wallabag/CoreBundle/Resources/config/services.yml b/src/Wallabag/CoreBundle/Resources/config/services.yml index 65c2c8d8..8e21b052 100644 --- a/src/Wallabag/CoreBundle/Resources/config/services.yml +++ b/src/Wallabag/CoreBundle/Resources/config/services.yml | |||
@@ -64,3 +64,9 @@ services: | |||
64 | - %language% | 64 | - %language% |
65 | tags: | 65 | tags: |
66 | - { name: kernel.event_subscriber } | 66 | - { name: kernel.event_subscriber } |
67 | |||
68 | wallabag_core.helper.entries_export: | ||
69 | class: Wallabag\CoreBundle\Helper\EntriesExport | ||
70 | arguments: | ||
71 | - %wallabag_url% | ||
72 | - src/Wallabag/CoreBundle/Resources/views/themes/_global/public/img/appicon/apple-touch-icon-152.png | ||
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/entries.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/entries.html.twig index 668824bc..bf38bff8 100644 --- a/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/entries.html.twig +++ b/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/entries.html.twig | |||
@@ -91,6 +91,24 @@ | |||
91 | {% endfor %} | 91 | {% endfor %} |
92 | </ul> | 92 | </ul> |
93 | 93 | ||
94 | <!-- Export --> | ||
95 | <div id="export" class="side-nav fixed right-aligned"> | ||
96 | {% set currentRoute = app.request.attributes.get('_route') %} | ||
97 | {% if currentRoute == 'homepage' %} | ||
98 | {% set currentRoute = 'unread' %} | ||
99 | {% endif %} | ||
100 | <h4 class="center">{% trans %}Export{% endtrans %}</h4> | ||
101 | <ul> | ||
102 | <li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'epub' }) }}">{% trans %}EPUB{% endtrans %}</a></li> | ||
103 | <li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'mobi' }) }}">{% trans %}MOBI{% endtrans %}</a></li> | ||
104 | <li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'pdf' }) }}">{% trans %}PDF{% endtrans %}</a></li> | ||
105 | <li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'xml' }) }}">{% trans %}XML{% endtrans %}</a></li> | ||
106 | <li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'json' }) }}">{% trans %}JSON{% endtrans %}</a></li> | ||
107 | <li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'csv' }) }}">{% trans %}CSV{% endtrans %}</a></li> | ||
108 | <li class="bold"><del><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'txt' }) }}">{% trans %}TXT{% endtrans %}</a></del></li> | ||
109 | </ul> | ||
110 | </div> | ||
111 | |||
94 | <!-- Filters --> | 112 | <!-- Filters --> |
95 | <div id="filters" class="side-nav fixed right-aligned"> | 113 | <div id="filters" class="side-nav fixed right-aligned"> |
96 | <form action="{{ path('all') }}"> | 114 | <form action="{{ path('all') }}"> |
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 7230506c..fd84d984 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 | |||
@@ -102,13 +102,16 @@ | |||
102 | <li class="bold"> | 102 | <li class="bold"> |
103 | <a class="waves-effect collapsible-header"> | 103 | <a class="waves-effect collapsible-header"> |
104 | <i class="mdi-file-file-download small"></i> | 104 | <i class="mdi-file-file-download small"></i> |
105 | <span><del>{% trans %}Download{% endtrans %}</del></span> | 105 | <span>{% trans %}Download{% endtrans %}</span> |
106 | </a> | 106 | </a> |
107 | <div class="collapsible-body"> | 107 | <div class="collapsible-body"> |
108 | <ul> | 108 | <ul> |
109 | {% if export_epub %}<li><del><a href="?epub&method=id&value={{ entry.id }}" title="Generate ePub file">EPUB</a></del></li>{% endif %} | 109 | {% if export_epub %}<li><a href="{{ path('export_entry', { 'id': entry.id, 'format': 'epub' }) }}" title="Generate ePub file">EPUB</a></li>{% endif %} |
110 | {% if export_mobi %}<li><del><a href="?mobi&method=id&value={{ entry.id }}" title="Generate Mobi file">MOBI</a></del></li>{% endif %} | 110 | {% if export_mobi %}<li><a href="{{ path('export_entry', { 'id': entry.id, 'format': 'mobi' }) }}" title="Generate Mobi file">MOBI</a></li>{% endif %} |
111 | {% if export_pdf %}<li><del><a href="?pdf&method=id&value={{ entry.id }}" title="Generate PDF file">PDF</a></del> </li>{% endif %} | 111 | {% if export_pdf %}<li><a href="{{ path('export_entry', { 'id': entry.id, 'format': 'pdf' }) }}" title="Generate PDF file">PDF</a></li>{% endif %} |
112 | <li><a href="{{ path('export_entry', { 'id': entry.id, 'format': 'csv' }) }}" title="Generate CSV file">CSV</a></li> | ||
113 | <li><a href="{{ path('export_entry', { 'id': entry.id, 'format': 'json' }) }}" title="Generate JSON file">JSON</a></li> | ||
114 | <li><a href="{{ path('export_entry', { 'id': entry.id, 'format': 'xml' }) }}" title="Generate XML file">XML</a></li> | ||
112 | </ul> | 115 | </ul> |
113 | </div> | 116 | </div> |
114 | </li> | 117 | </li> |
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/material/layout.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/material/layout.html.twig index 95b3977c..f426e25b 100644 --- a/src/Wallabag/CoreBundle/Resources/views/themes/material/layout.html.twig +++ b/src/Wallabag/CoreBundle/Resources/views/themes/material/layout.html.twig | |||
@@ -59,6 +59,7 @@ | |||
59 | <li class="bold"><a title="{% trans %}Add a new entry{% endtrans %}" class="waves-effect" href="{{ path('new') }}" id="nav-btn-add"><i class="mdi-content-add"></i></a></li> | 59 | <li class="bold"><a title="{% trans %}Add a new entry{% endtrans %}" class="waves-effect" href="{{ path('new') }}" id="nav-btn-add"><i class="mdi-content-add"></i></a></li> |
60 | <li><a title="{% trans %}Search{% endtrans %}" class="waves-effect" href="javascript: void(null);" id="nav-btn-search"><i class="mdi-action-search"></i></a> | 60 | <li><a title="{% trans %}Search{% endtrans %}" class="waves-effect" href="javascript: void(null);" id="nav-btn-search"><i class="mdi-action-search"></i></a> |
61 | <li id="button_filters"><a title="{% trans %}Filter entries{% endtrans %}" href="#" data-activates="filters" class="nav-panel-menu button-collapse-right"><i class="mdi-content-filter-list"></i></a></li> | 61 | <li id="button_filters"><a title="{% trans %}Filter entries{% endtrans %}" href="#" data-activates="filters" class="nav-panel-menu button-collapse-right"><i class="mdi-content-filter-list"></i></a></li> |
62 | <li id="button_export"><a title="{% trans %}Export{% endtrans %}" class="nav-panel-menu button-collapse-right" href="#" data-activates="export" class="nav-panel-menu button-collapse-right"><i class="mdi-file-file-download"></i></a></li> | ||
62 | </ul> | 63 | </ul> |
63 | </div> | 64 | </div> |
64 | <form method="get" action="index.php"> | 65 | <form method="get" action="index.php"> |
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/material/public/js/init.js b/src/Wallabag/CoreBundle/Resources/views/themes/material/public/js/init.js index edfdee82..491a7916 100755 --- a/src/Wallabag/CoreBundle/Resources/views/themes/material/public/js/init.js +++ b/src/Wallabag/CoreBundle/Resources/views/themes/material/public/js/init.js | |||
@@ -11,6 +11,14 @@ function init_filters() { | |||
11 | } | 11 | } |
12 | } | 12 | } |
13 | 13 | ||
14 | function init_export() { | ||
15 | // no display if export not aviable | ||
16 | if ($("div").is("#export")) { | ||
17 | $('#button_export').show(); | ||
18 | $('.button-collapse-right').sideNav({ edge: 'right' }); | ||
19 | } | ||
20 | } | ||
21 | |||
14 | $(document).ready(function(){ | 22 | $(document).ready(function(){ |
15 | // sideNav | 23 | // sideNav |
16 | $('.button-collapse').sideNav(); | 24 | $('.button-collapse').sideNav(); |
@@ -26,6 +34,7 @@ $(document).ready(function(){ | |||
26 | format: 'dd/mm/yyyy', | 34 | format: 'dd/mm/yyyy', |
27 | }); | 35 | }); |
28 | init_filters(); | 36 | init_filters(); |
37 | init_export(); | ||
29 | 38 | ||
30 | $('#nav-btn-add-tag').on('click', function(){ | 39 | $('#nav-btn-add-tag').on('click', function(){ |
31 | $(".nav-panel-add-tag").toggle(100); | 40 | $(".nav-panel-add-tag").toggle(100); |
diff --git a/src/Wallabag/CoreBundle/Tests/Controller/ExportControllerTest.php b/src/Wallabag/CoreBundle/Tests/Controller/ExportControllerTest.php new file mode 100644 index 00000000..739b2dec --- /dev/null +++ b/src/Wallabag/CoreBundle/Tests/Controller/ExportControllerTest.php | |||
@@ -0,0 +1,234 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Wallabag\CoreBundle\Tests\Controller; | ||
4 | |||
5 | use Wallabag\CoreBundle\Tests\WallabagCoreTestCase; | ||
6 | |||
7 | class ExportControllerTest extends WallabagCoreTestCase | ||
8 | { | ||
9 | public function testLogin() | ||
10 | { | ||
11 | $client = $this->getClient(); | ||
12 | |||
13 | $client->request('GET', '/export/unread.csv'); | ||
14 | |||
15 | $this->assertEquals(302, $client->getResponse()->getStatusCode()); | ||
16 | $this->assertContains('login', $client->getResponse()->headers->get('location')); | ||
17 | } | ||
18 | |||
19 | public function testUnknownCategoryExport() | ||
20 | { | ||
21 | $this->logInAs('admin'); | ||
22 | $client = $this->getClient(); | ||
23 | |||
24 | $client->request('GET', '/export/awesomeness.epub'); | ||
25 | |||
26 | $this->assertEquals(404, $client->getResponse()->getStatusCode()); | ||
27 | } | ||
28 | |||
29 | public function testUnknownFormatExport() | ||
30 | { | ||
31 | $this->logInAs('admin'); | ||
32 | $client = $this->getClient(); | ||
33 | |||
34 | $client->request('GET', '/export/unread.xslx'); | ||
35 | |||
36 | $this->assertEquals(404, $client->getResponse()->getStatusCode()); | ||
37 | } | ||
38 | |||
39 | public function testUnsupportedFormatExport() | ||
40 | { | ||
41 | $this->logInAs('admin'); | ||
42 | $client = $this->getClient(); | ||
43 | |||
44 | $client->request('GET', '/export/unread.txt'); | ||
45 | $this->assertEquals(404, $client->getResponse()->getStatusCode()); | ||
46 | |||
47 | $content = $client->getContainer() | ||
48 | ->get('doctrine.orm.entity_manager') | ||
49 | ->getRepository('WallabagCoreBundle:Entry') | ||
50 | ->findOneByUsernameAndNotArchived('admin'); | ||
51 | |||
52 | $client->request('GET', '/export/'.$content->getId().'.txt'); | ||
53 | $this->assertEquals(404, $client->getResponse()->getStatusCode()); | ||
54 | } | ||
55 | |||
56 | public function testBadEntryId() | ||
57 | { | ||
58 | $this->logInAs('admin'); | ||
59 | $client = $this->getClient(); | ||
60 | |||
61 | $client->request('GET', '/export/0.mobi'); | ||
62 | |||
63 | $this->assertEquals(404, $client->getResponse()->getStatusCode()); | ||
64 | } | ||
65 | |||
66 | public function testEpubExport() | ||
67 | { | ||
68 | $this->logInAs('admin'); | ||
69 | $client = $this->getClient(); | ||
70 | |||
71 | ob_start(); | ||
72 | $crawler = $client->request('GET', '/export/archive.epub'); | ||
73 | ob_end_clean(); | ||
74 | |||
75 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); | ||
76 | |||
77 | $headers = $client->getResponse()->headers; | ||
78 | $this->assertEquals('application/epub+zip', $headers->get('content-type')); | ||
79 | $this->assertEquals('attachment; filename="Archive articles.epub"', $headers->get('content-disposition')); | ||
80 | $this->assertEquals('binary', $headers->get('content-transfer-encoding')); | ||
81 | } | ||
82 | |||
83 | public function testMobiExport() | ||
84 | { | ||
85 | $this->logInAs('admin'); | ||
86 | $client = $this->getClient(); | ||
87 | |||
88 | $content = $client->getContainer() | ||
89 | ->get('doctrine.orm.entity_manager') | ||
90 | ->getRepository('WallabagCoreBundle:Entry') | ||
91 | ->findOneByUsernameAndNotArchived('admin'); | ||
92 | |||
93 | ob_start(); | ||
94 | $crawler = $client->request('GET', '/export/'.$content->getId().'.mobi'); | ||
95 | ob_end_clean(); | ||
96 | |||
97 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); | ||
98 | |||
99 | $headers = $client->getResponse()->headers; | ||
100 | $this->assertEquals('application/x-mobipocket-ebook', $headers->get('content-type')); | ||
101 | $this->assertEquals('attachment; filename="'.preg_replace('/[^A-Za-z0-9\-]/', '', $content->getTitle()).'.mobi"', $headers->get('content-disposition')); | ||
102 | $this->assertEquals('binary', $headers->get('content-transfer-encoding')); | ||
103 | } | ||
104 | |||
105 | public function testPdfExport() | ||
106 | { | ||
107 | $this->logInAs('admin'); | ||
108 | $client = $this->getClient(); | ||
109 | |||
110 | ob_start(); | ||
111 | $crawler = $client->request('GET', '/export/all.pdf'); | ||
112 | ob_end_clean(); | ||
113 | |||
114 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); | ||
115 | |||
116 | $headers = $client->getResponse()->headers; | ||
117 | $this->assertEquals('application/pdf', $headers->get('content-type')); | ||
118 | $this->assertEquals('attachment; filename="All articles.pdf"', $headers->get('content-disposition')); | ||
119 | $this->assertEquals('binary', $headers->get('content-transfer-encoding')); | ||
120 | } | ||
121 | |||
122 | public function testCsvExport() | ||
123 | { | ||
124 | $this->logInAs('admin'); | ||
125 | $client = $this->getClient(); | ||
126 | |||
127 | // to be sure results are the same | ||
128 | $contentInDB = $client->getContainer() | ||
129 | ->get('doctrine.orm.entity_manager') | ||
130 | ->getRepository('WallabagCoreBundle:Entry') | ||
131 | ->createQueryBuilder('e') | ||
132 | ->leftJoin('e.user', 'u') | ||
133 | ->where('u.username = :username')->setParameter('username', 'admin') | ||
134 | ->andWhere('e.isArchived = true') | ||
135 | ->getQuery() | ||
136 | ->getArrayResult(); | ||
137 | |||
138 | ob_start(); | ||
139 | $crawler = $client->request('GET', '/export/archive.csv'); | ||
140 | ob_end_clean(); | ||
141 | |||
142 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); | ||
143 | |||
144 | $headers = $client->getResponse()->headers; | ||
145 | $this->assertEquals('application/csv', $headers->get('content-type')); | ||
146 | $this->assertEquals('attachment; filename="Archive articles.csv"', $headers->get('content-disposition')); | ||
147 | $this->assertEquals('UTF-8', $headers->get('content-transfer-encoding')); | ||
148 | |||
149 | $csv = str_getcsv($client->getResponse()->getContent(), "\n"); | ||
150 | |||
151 | $this->assertGreaterThan(1, $csv); | ||
152 | // +1 for title line | ||
153 | $this->assertEquals(count($contentInDB)+1, count($csv)); | ||
154 | $this->assertEquals('Title;URL;Content;Tags;"MIME Type";Language', $csv[0]); | ||
155 | } | ||
156 | |||
157 | public function testJsonExport() | ||
158 | { | ||
159 | $this->logInAs('admin'); | ||
160 | $client = $this->getClient(); | ||
161 | |||
162 | // to be sure results are the same | ||
163 | $contentInDB = $client->getContainer() | ||
164 | ->get('doctrine.orm.entity_manager') | ||
165 | ->getRepository('WallabagCoreBundle:Entry') | ||
166 | ->createQueryBuilder('e') | ||
167 | ->leftJoin('e.user', 'u') | ||
168 | ->where('u.username = :username')->setParameter('username', 'admin') | ||
169 | ->getQuery() | ||
170 | ->getArrayResult(); | ||
171 | |||
172 | ob_start(); | ||
173 | $crawler = $client->request('GET', '/export/all.json'); | ||
174 | ob_end_clean(); | ||
175 | |||
176 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); | ||
177 | |||
178 | $headers = $client->getResponse()->headers; | ||
179 | $this->assertEquals('application/json', $headers->get('content-type')); | ||
180 | $this->assertEquals('attachment; filename="All articles.json"', $headers->get('content-disposition')); | ||
181 | $this->assertEquals('UTF-8', $headers->get('content-transfer-encoding')); | ||
182 | |||
183 | $content = json_decode($client->getResponse()->getContent(), true); | ||
184 | $this->assertEquals(count($contentInDB), count($content)); | ||
185 | $this->assertArrayHasKey('id', $content[0]); | ||
186 | $this->assertArrayHasKey('title', $content[0]); | ||
187 | $this->assertArrayHasKey('url', $content[0]); | ||
188 | $this->assertArrayHasKey('is_archived', $content[0]); | ||
189 | $this->assertArrayHasKey('is_starred', $content[0]); | ||
190 | $this->assertArrayHasKey('content', $content[0]); | ||
191 | $this->assertArrayHasKey('mimetype', $content[0]); | ||
192 | $this->assertArrayHasKey('language', $content[0]); | ||
193 | $this->assertArrayHasKey('reading_time', $content[0]); | ||
194 | $this->assertArrayHasKey('domain_name', $content[0]); | ||
195 | $this->assertArrayHasKey('tags', $content[0]); | ||
196 | } | ||
197 | |||
198 | public function testXmlExport() | ||
199 | { | ||
200 | $this->logInAs('admin'); | ||
201 | $client = $this->getClient(); | ||
202 | |||
203 | // to be sure results are the same | ||
204 | $contentInDB = $client->getContainer() | ||
205 | ->get('doctrine.orm.entity_manager') | ||
206 | ->getRepository('WallabagCoreBundle:Entry') | ||
207 | ->createQueryBuilder('e') | ||
208 | ->leftJoin('e.user', 'u') | ||
209 | ->where('u.username = :username')->setParameter('username', 'admin') | ||
210 | ->andWhere('e.isArchived = false') | ||
211 | ->getQuery() | ||
212 | ->getArrayResult(); | ||
213 | |||
214 | ob_start(); | ||
215 | $crawler = $client->request('GET', '/export/unread.xml'); | ||
216 | ob_end_clean(); | ||
217 | |||
218 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); | ||
219 | |||
220 | $headers = $client->getResponse()->headers; | ||
221 | $this->assertEquals('application/xml', $headers->get('content-type')); | ||
222 | $this->assertEquals('attachment; filename="Unread articles.xml"', $headers->get('content-disposition')); | ||
223 | $this->assertEquals('UTF-8', $headers->get('content-transfer-encoding')); | ||
224 | |||
225 | $content = new \SimpleXMLElement($client->getResponse()->getContent()); | ||
226 | $this->assertGreaterThan(0, $content->count()); | ||
227 | $this->assertEquals(count($contentInDB), $content->count()); | ||
228 | $this->assertNotEmpty('id', (string) $content->entry[0]->id); | ||
229 | $this->assertNotEmpty('title', (string) $content->entry[0]->title); | ||
230 | $this->assertNotEmpty('url', (string) $content->entry[0]->url); | ||
231 | $this->assertNotEmpty('content', (string) $content->entry[0]->content); | ||
232 | $this->assertNotEmpty('domain_name', (string) $content->entry[0]->domain_name); | ||
233 | } | ||
234 | } | ||