diff options
author | Jeremy Benoist <jeremy.benoist@gmail.com> | 2015-10-16 10:51:53 +0200 |
---|---|---|
committer | Nicolas LÅ“uillet <nicolas.loeuillet@smile.fr> | 2015-11-09 16:32:48 +0100 |
commit | add597bad95b30dbecab3aecc8362a1ccd427976 (patch) | |
tree | 1e4976f41fb043bea41c4db4544b42458eee3500 /src/Wallabag | |
parent | 03690d138792dde6405e3d2eb3c53f6572eb3c43 (diff) | |
download | wallabag-add597bad95b30dbecab3aecc8362a1ccd427976.tar.gz wallabag-add597bad95b30dbecab3aecc8362a1ccd427976.tar.zst wallabag-add597bad95b30dbecab3aecc8362a1ccd427976.zip |
Rework on export
- all export now return a `HttpFoundation\Response`
- return a 404 on unsupported format
- add tests
Diffstat (limited to 'src/Wallabag')
4 files changed, 324 insertions, 133 deletions
diff --git a/src/Wallabag/CoreBundle/Controller/ExportController.php b/src/Wallabag/CoreBundle/Controller/ExportController.php index 123e491a..dd3cb7ca 100644 --- a/src/Wallabag/CoreBundle/Controller/ExportController.php +++ b/src/Wallabag/CoreBundle/Controller/ExportController.php | |||
@@ -4,62 +4,55 @@ namespace Wallabag\CoreBundle\Controller; | |||
4 | 4 | ||
5 | use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; | 5 | use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; |
6 | use Symfony\Bundle\FrameworkBundle\Controller\Controller; | 6 | use Symfony\Bundle\FrameworkBundle\Controller\Controller; |
7 | use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; | ||
7 | use Wallabag\CoreBundle\Entity\Entry; | 8 | use Wallabag\CoreBundle\Entity\Entry; |
8 | use Wallabag\CoreBundle\Helper\EntriesExport; | ||
9 | 9 | ||
10 | class ExportController extends Controller | 10 | class ExportController extends Controller |
11 | { | 11 | { |
12 | /** | 12 | /** |
13 | * Gets all entries for current user. | 13 | * Gets one entry content. |
14 | * | 14 | * |
15 | * @Route("/export/{category}.{format}", name="ebook", requirements={ | 15 | * @param Entry $entry |
16 | * "_format": "epub|mobi|pdf|json|xml|txt|csv" | 16 | * |
17 | * }) | 17 | * @Route("/export/{id}.{format}", requirements={"id" = "\d+"}, name="export_entry") |
18 | */ | 18 | */ |
19 | public function getEntriesAction($format, $category) | 19 | public function downloadEntryAction(Entry $entry, $format) |
20 | { | 20 | { |
21 | $repository = $this->getDoctrine()->getRepository('WallabagCoreBundle:Entry'); | 21 | try { |
22 | switch ($category) { | 22 | return $this->get('wallabag_core.helper.entries_export') |
23 | case 'all': | 23 | ->setEntries($entry) |
24 | $method = 'All'; | 24 | ->updateTitle('entry') |
25 | break; | 25 | ->exportAs($format); |
26 | 26 | } catch (\InvalidArgumentException $e) { | |
27 | case 'unread': | 27 | throw new NotFoundHttpException($e->getMessage()); |
28 | $method = 'Unread'; | ||
29 | break; | ||
30 | |||
31 | case 'starred': | ||
32 | $method = 'Starred'; | ||
33 | break; | ||
34 | |||
35 | case 'archive': | ||
36 | $method = 'Archive'; | ||
37 | break; | ||
38 | |||
39 | default: | ||
40 | break; | ||
41 | } | 28 | } |
42 | |||
43 | $methodBuilder = 'getBuilderFor'.$method.'ByUser'; | ||
44 | $qb = $repository->$methodBuilder($this->getUser()->getId()); | ||
45 | $entries = $qb->getQuery()->getResult(); | ||
46 | |||
47 | $export = new EntriesExport($entries); | ||
48 | $export->setMethod($method); | ||
49 | $export->exportAs($format); | ||
50 | } | 29 | } |
51 | 30 | ||
52 | /** | 31 | /** |
53 | * Gets one entry content. | 32 | * Export all entries for current user. |
54 | * | ||
55 | * @param Entry $entry | ||
56 | * | 33 | * |
57 | * @Route("/export/id/{id}.{format}", requirements={"id" = "\d+"}, name="ebook_entry") | 34 | * @Route("/export/{category}.{format}", name="export_entries", requirements={ |
35 | * "_format": "epub|mobi|pdf|json|xml|txt|csv", | ||
36 | * "category": "all|unread|starred|archive" | ||
37 | * }) | ||
58 | */ | 38 | */ |
59 | public function getEntryAction(Entry $entry, $format) | 39 | public function downloadEntriesAction($format, $category) |
60 | { | 40 | { |
61 | $export = new EntriesExport(array($entry)); | 41 | $method = ucfirst($category); |
62 | $export->setMethod('entry'); | 42 | $methodBuilder = 'getBuilderFor'.$method.'ByUser'; |
63 | $export->exportAs($format); | 43 | $entries = $this->getDoctrine() |
44 | ->getRepository('WallabagCoreBundle:Entry') | ||
45 | ->$methodBuilder($this->getUser()->getId()) | ||
46 | ->getQuery() | ||
47 | ->getResult(); | ||
48 | |||
49 | try { | ||
50 | return $this->get('wallabag_core.helper.entries_export') | ||
51 | ->setEntries($entries) | ||
52 | ->updateTitle($method) | ||
53 | ->exportAs($format); | ||
54 | } catch (\InvalidArgumentException $e) { | ||
55 | throw new NotFoundHttpException($e->getMessage()); | ||
56 | } | ||
64 | } | 57 | } |
65 | } | 58 | } |
diff --git a/src/Wallabag/CoreBundle/Helper/EntriesExport.php b/src/Wallabag/CoreBundle/Helper/EntriesExport.php index fad0bb97..806319b1 100644 --- a/src/Wallabag/CoreBundle/Helper/EntriesExport.php +++ b/src/Wallabag/CoreBundle/Helper/EntriesExport.php | |||
@@ -4,27 +4,51 @@ namespace Wallabag\CoreBundle\Helper; | |||
4 | 4 | ||
5 | use PHPePub\Core\EPub; | 5 | use PHPePub\Core\EPub; |
6 | use PHPePub\Core\Structure\OPF\DublinCore; | 6 | use PHPePub\Core\Structure\OPF\DublinCore; |
7 | use Symfony\Component\HttpFoundation\Response; | ||
7 | 8 | ||
8 | class EntriesExport | 9 | class EntriesExport |
9 | { | 10 | { |
10 | private $format; | 11 | private $wallabagUrl; |
11 | private $method; | 12 | private $logoPath; |
12 | private $title; | 13 | private $title = ''; |
13 | private $entries; | 14 | private $entries = array(); |
14 | private $authors = array('wallabag'); | 15 | private $authors = array('wallabag'); |
15 | private $language; | 16 | private $language = ''; |
16 | private $tags; | 17 | private $tags = array(); |
18 | private $footerTemplate = '<div style="text-align:center;"> | ||
19 | <p>Produced by wallabag with %EXPORT_METHOD%</p> | ||
20 | <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> | ||
21 | </div'; | ||
17 | 22 | ||
18 | public function __construct($entries) | 23 | /** |
24 | * @param string $wallabagUrl Wallabag instance url | ||
25 | * @param string $logoPath Path to the logo FROM THE BUNDLE SCOPE | ||
26 | */ | ||
27 | public function __construct($wallabagUrl, $logoPath) | ||
19 | { | 28 | { |
29 | $this->wallabagUrl = $wallabagUrl; | ||
30 | $this->logoPath = $logoPath; | ||
31 | } | ||
32 | |||
33 | /** | ||
34 | * Define entries. | ||
35 | * | ||
36 | * @param array|Entry $entries An array of entries or one entry | ||
37 | */ | ||
38 | public function setEntries($entries) | ||
39 | { | ||
40 | if (!is_array($entries)) { | ||
41 | $this->language = $entries->getLanguage(); | ||
42 | $entries = array($entries); | ||
43 | } | ||
44 | |||
20 | $this->entries = $entries; | 45 | $this->entries = $entries; |
21 | 46 | ||
22 | foreach ($entries as $entry) { | 47 | foreach ($entries as $entry) { |
23 | $this->tags[] = $entry->getTags(); | 48 | $this->tags[] = $entry->getTags(); |
24 | } | 49 | } |
25 | if (count($entries) === 1) { | 50 | |
26 | $this->language = $entries[0]->getLanguage(); | 51 | return $this; |
27 | } | ||
28 | } | 52 | } |
29 | 53 | ||
30 | /** | 54 | /** |
@@ -32,29 +56,15 @@ class EntriesExport | |||
32 | * | 56 | * |
33 | * @param string $method Method to get articles | 57 | * @param string $method Method to get articles |
34 | */ | 58 | */ |
35 | public function setMethod($method) | 59 | public function updateTitle($method) |
36 | { | 60 | { |
37 | $this->method = $method; | 61 | $this->title = $method.' articles'; |
38 | 62 | ||
39 | switch ($this->method) { | 63 | if ('entry' === $method) { |
40 | case 'All': | 64 | $this->title = $this->entries[0]->getTitle(); |
41 | $this->title = 'All Articles'; | ||
42 | break; | ||
43 | case 'Unread': | ||
44 | $this->title = 'Unread articles'; | ||
45 | break; | ||
46 | case 'Starred': | ||
47 | $this->title = 'Starred articles'; | ||
48 | break; | ||
49 | case 'Archive': | ||
50 | $this->title = 'Archived articles'; | ||
51 | break; | ||
52 | case 'entry': | ||
53 | $this->title = $this->entries[0]->getTitle(); | ||
54 | break; | ||
55 | default: | ||
56 | break; | ||
57 | } | 65 | } |
66 | |||
67 | return $this; | ||
58 | } | 68 | } |
59 | 69 | ||
60 | /** | 70 | /** |
@@ -64,30 +74,26 @@ class EntriesExport | |||
64 | */ | 74 | */ |
65 | public function exportAs($format) | 75 | public function exportAs($format) |
66 | { | 76 | { |
67 | $this->format = $format; | 77 | switch ($format) { |
68 | |||
69 | switch ($this->format) { | ||
70 | case 'epub': | 78 | case 'epub': |
71 | $this->produceEpub(); | 79 | return $this->produceEpub(); |
72 | break; | ||
73 | 80 | ||
74 | case 'mobi': | 81 | case 'mobi': |
75 | $this->produceMobi(); | 82 | return $this->produceMobi(); |
76 | break; | ||
77 | 83 | ||
78 | case 'pdf': | 84 | case 'pdf': |
79 | $this->producePDF(); | 85 | return $this->producePDF(); |
80 | break; | ||
81 | 86 | ||
82 | case 'csv': | 87 | case 'csv': |
83 | $this->produceCSV(); | 88 | return $this->produceCSV(); |
84 | break; | ||
85 | |||
86 | default: | ||
87 | break; | ||
88 | } | 89 | } |
90 | |||
91 | throw new \InvalidArgumentException(sprintf('The format "%s" is not yet supported.', $format)); | ||
89 | } | 92 | } |
90 | 93 | ||
94 | /** | ||
95 | * Use PHPePub to dump a .epub file. | ||
96 | */ | ||
91 | private function produceEpub() | 97 | private function produceEpub() |
92 | { | 98 | { |
93 | /* | 99 | /* |
@@ -98,7 +104,7 @@ class EntriesExport | |||
98 | ."<html xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:epub=\"http://www.idpf.org/2007/ops\">\n" | 104 | ."<html xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:epub=\"http://www.idpf.org/2007/ops\">\n" |
99 | .'<head>' | 105 | .'<head>' |
100 | ."<meta http-equiv=\"Default-Style\" content=\"text/html; charset=utf-8\" />\n" | 106 | ."<meta http-equiv=\"Default-Style\" content=\"text/html; charset=utf-8\" />\n" |
101 | .'<title>'._('wallabag articles book')."</title>\n" | 107 | ."<title>wallabag articles book</title>\n" |
102 | ."</head>\n" | 108 | ."</head>\n" |
103 | ."<body>\n"; | 109 | ."<body>\n"; |
104 | 110 | ||
@@ -111,17 +117,21 @@ class EntriesExport | |||
111 | */ | 117 | */ |
112 | 118 | ||
113 | $book->setTitle($this->title); | 119 | $book->setTitle($this->title); |
114 | $book->setIdentifier($this->title, EPub::IDENTIFIER_URI); // Could also be the ISBN number, prefered for published books, or a UUID. | 120 | // Could also be the ISBN number, prefered for published books, or a UUID. |
115 | $book->setLanguage($this->language); // 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. | 121 | $book->setIdentifier($this->title, EPub::IDENTIFIER_URI); |
116 | $book->setDescription(_('Some articles saved on my wallabag')); | 122 | // 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. |
123 | $book->setLanguage($this->language); | ||
124 | $book->setDescription('Some articles saved on my wallabag'); | ||
117 | 125 | ||
118 | foreach ($this->authors as $author) { | 126 | foreach ($this->authors as $author) { |
119 | $book->setAuthor($author, $author); | 127 | $book->setAuthor($author, $author); |
120 | } | 128 | } |
121 | 129 | ||
122 | $book->setPublisher('wallabag', 'wallabag'); // I hope this is a non existant address :) | 130 | // I hope this is a non existant address :) |
123 | $book->setDate(time()); // Strictly not needed as the book date defaults to time(). | 131 | $book->setPublisher('wallabag', 'wallabag'); |
124 | $book->setSourceURL("http://$_SERVER[HTTP_HOST]"); | 132 | // Strictly not needed as the book date defaults to time(). |
133 | $book->setDate(time()); | ||
134 | $book->setSourceURL($this->wallabagUrl); | ||
125 | 135 | ||
126 | $book->addDublinCoreMetadata(DublinCore::CONTRIBUTOR, 'PHP'); | 136 | $book->addDublinCoreMetadata(DublinCore::CONTRIBUTOR, 'PHP'); |
127 | $book->addDublinCoreMetadata(DublinCore::CONTRIBUTOR, 'wallabag'); | 137 | $book->addDublinCoreMetadata(DublinCore::CONTRIBUTOR, 'wallabag'); |
@@ -129,12 +139,11 @@ class EntriesExport | |||
129 | /* | 139 | /* |
130 | * Front page | 140 | * Front page |
131 | */ | 141 | */ |
142 | if (file_exists($this->logoPath)) { | ||
143 | $book->setCoverImage('Cover.png', file_get_contents($this->logoPath), 'image/png'); | ||
144 | } | ||
132 | 145 | ||
133 | $book->setCoverImage('Cover.png', file_get_contents('themes/_global/img/appicon/apple-touch-icon-152.png'), 'image/png'); | 146 | $book->addChapter('Notices', 'Cover2.html', $content_start.$this->getExportInformation('PHPePub').$bookEnd); |
134 | |||
135 | $cover = $content_start.'<div style="text-align:center;"><p>'._('Produced by wallabag with PHPePub').'</p><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></div>'.$bookEnd; | ||
136 | |||
137 | $book->addChapter('Notices', 'Cover2.html', $cover); | ||
138 | 147 | ||
139 | $book->buildTOC(); | 148 | $book->buildTOC(); |
140 | 149 | ||
@@ -142,18 +151,31 @@ class EntriesExport | |||
142 | * Adding actual entries | 151 | * Adding actual entries |
143 | */ | 152 | */ |
144 | 153 | ||
145 | foreach ($this->entries as $entry) { //set tags as subjects | 154 | // set tags as subjects |
146 | foreach ($this->tags as $tag) { | 155 | foreach ($this->entries as $entry) { |
147 | $book->setSubject($tag['value']); | 156 | foreach ($this->tags as $tag) { |
148 | } | 157 | $book->setSubject($tag['value']); |
158 | } | ||
149 | 159 | ||
150 | $chapter = $content_start.$entry->getContent().$bookEnd; | 160 | $chapter = $content_start.$entry->getContent().$bookEnd; |
151 | $book->addChapter($entry->getTitle(), htmlspecialchars($entry->getTitle()).'.html', $chapter, true, EPub::EXTERNAL_REF_ADD); | 161 | $book->addChapter($entry->getTitle(), htmlspecialchars($entry->getTitle()).'.html', $chapter, true, EPub::EXTERNAL_REF_ADD); |
152 | } | 162 | } |
153 | $book->finalize(); | 163 | |
154 | $book->sendBook($this->title); | 164 | return Response::create( |
165 | $book->getBook(), | ||
166 | 200, | ||
167 | array( | ||
168 | 'Content-Description' => 'File Transfer', | ||
169 | 'Content-type' => 'application/epub+zip', | ||
170 | 'Content-Disposition' => 'attachment; filename="'.$this->title.'.epub"', | ||
171 | 'Content-Transfer-Encoding' => 'binary', | ||
172 | ) | ||
173 | )->send(); | ||
155 | } | 174 | } |
156 | 175 | ||
176 | /** | ||
177 | * Use PHPMobi to dump a .mobi file. | ||
178 | */ | ||
157 | private function produceMobi() | 179 | private function produceMobi() |
158 | { | 180 | { |
159 | $mobi = new \MOBI(); | 181 | $mobi = new \MOBI(); |
@@ -162,7 +184,6 @@ class EntriesExport | |||
162 | /* | 184 | /* |
163 | * Book metadata | 185 | * Book metadata |
164 | */ | 186 | */ |
165 | |||
166 | $content->set('title', $this->title); | 187 | $content->set('title', $this->title); |
167 | $content->set('author', implode($this->authors)); | 188 | $content->set('author', implode($this->authors)); |
168 | $content->set('subject', $this->title); | 189 | $content->set('subject', $this->title); |
@@ -170,15 +191,15 @@ class EntriesExport | |||
170 | /* | 191 | /* |
171 | * Front page | 192 | * Front page |
172 | */ | 193 | */ |
173 | 194 | $content->appendParagraph($this->getExportInformation('PHPMobi')); | |
174 | $content->appendParagraph('<div style="text-align:center;" ><p>'._('Produced by wallabag with PHPMobi').'</p><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></div>'); | 195 | if (file_exists($this->logoPath)) { |
175 | $content->appendImage(imagecreatefrompng('themes/_global/img/appicon/apple-touch-icon-152.png')); | 196 | $content->appendImage(imagecreatefrompng($this->logoPath)); |
197 | } | ||
176 | $content->appendPageBreak(); | 198 | $content->appendPageBreak(); |
177 | 199 | ||
178 | /* | 200 | /* |
179 | * Adding actual entries | 201 | * Adding actual entries |
180 | */ | 202 | */ |
181 | |||
182 | foreach ($this->entries as $entry) { | 203 | foreach ($this->entries as $entry) { |
183 | $content->appendChapterTitle($entry->getTitle()); | 204 | $content->appendChapterTitle($entry->getTitle()); |
184 | $content->appendParagraph($entry->getContent()); | 205 | $content->appendParagraph($entry->getContent()); |
@@ -189,10 +210,22 @@ class EntriesExport | |||
189 | // the browser inside Kindle Devices doesn't likes special caracters either, we limit to A-z/0-9 | 210 | // the browser inside Kindle Devices doesn't likes special caracters either, we limit to A-z/0-9 |
190 | $this->title = preg_replace('/[^A-Za-z0-9\-]/', '', $this->title); | 211 | $this->title = preg_replace('/[^A-Za-z0-9\-]/', '', $this->title); |
191 | 212 | ||
192 | // we offer file to download | 213 | return Response::create( |
193 | $mobi->download($this->title.'.mobi'); | 214 | $mobi->toString(), |
215 | 200, | ||
216 | array( | ||
217 | 'Accept-Ranges' => 'bytes', | ||
218 | 'Content-Description' => 'File Transfer', | ||
219 | 'Content-type' => 'application/x-mobipocket-ebook', | ||
220 | 'Content-Disposition' => 'attachment; filename="'.$this->title.'.mobi"', | ||
221 | 'Content-Transfer-Encoding' => 'binary', | ||
222 | ) | ||
223 | )->send(); | ||
194 | } | 224 | } |
195 | 225 | ||
226 | /** | ||
227 | * Use TCPDF to dump a .pdf file. | ||
228 | */ | ||
196 | private function producePDF() | 229 | private function producePDF() |
197 | { | 230 | { |
198 | $pdf = new \TCPDF(PDF_PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT, true, 'UTF-8', false); | 231 | $pdf = new \TCPDF(PDF_PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT, true, 'UTF-8', false); |
@@ -200,7 +233,6 @@ class EntriesExport | |||
200 | /* | 233 | /* |
201 | * Book metadata | 234 | * Book metadata |
202 | */ | 235 | */ |
203 | |||
204 | $pdf->SetCreator(PDF_CREATOR); | 236 | $pdf->SetCreator(PDF_CREATOR); |
205 | $pdf->SetAuthor('wallabag'); | 237 | $pdf->SetAuthor('wallabag'); |
206 | $pdf->SetTitle($this->title); | 238 | $pdf->SetTitle($this->title); |
@@ -210,19 +242,14 @@ class EntriesExport | |||
210 | /* | 242 | /* |
211 | * Front page | 243 | * Front page |
212 | */ | 244 | */ |
213 | |||
214 | $pdf->AddPage(); | 245 | $pdf->AddPage(); |
215 | $intro = '<h1>'.$this->title.'</h1><div style="text-align:center;" > | 246 | $intro = '<h1>'.$this->title.'</h1>'.$this->getExportInformation('tcpdf'); |
216 | <p>'._('Produced by wallabag with tcpdf').'</p> | ||
217 | <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> | ||
218 | <img src="themes/_global/img/appicon/apple-touch-icon-152.png" /></div>'; | ||
219 | 247 | ||
220 | $pdf->writeHTMLCell(0, 0, '', '', $intro, 0, 1, 0, true, '', true); | 248 | $pdf->writeHTMLCell(0, 0, '', '', $intro, 0, 1, 0, true, '', true); |
221 | 249 | ||
222 | /* | 250 | /* |
223 | * Adding actual entries | 251 | * Adding actual entries |
224 | */ | 252 | */ |
225 | |||
226 | foreach ($this->entries as $entry) { | 253 | foreach ($this->entries as $entry) { |
227 | foreach ($this->tags as $tag) { | 254 | foreach ($this->tags as $tag) { |
228 | $pdf->SetKeywords($tag['value']); | 255 | $pdf->SetKeywords($tag['value']); |
@@ -231,33 +258,82 @@ class EntriesExport | |||
231 | $pdf->AddPage(); | 258 | $pdf->AddPage(); |
232 | $html = '<h1>'.$entry->getTitle().'</h1>'; | 259 | $html = '<h1>'.$entry->getTitle().'</h1>'; |
233 | $html .= $entry->getContent(); | 260 | $html .= $entry->getContent(); |
261 | |||
234 | $pdf->writeHTMLCell(0, 0, '', '', $html, 0, 1, 0, true, '', true); | 262 | $pdf->writeHTMLCell(0, 0, '', '', $html, 0, 1, 0, true, '', true); |
235 | } | 263 | } |
236 | 264 | ||
237 | // set image scale factor | 265 | // set image scale factor |
238 | $pdf->setImageScale(PDF_IMAGE_SCALE_RATIO); | 266 | $pdf->setImageScale(PDF_IMAGE_SCALE_RATIO); |
239 | 267 | ||
240 | $pdf->Output($this->title.'.pdf', 'D'); | 268 | return Response::create( |
269 | $pdf->Output('', 'S'), | ||
270 | 200, | ||
271 | array( | ||
272 | 'Content-Description' => 'File Transfer', | ||
273 | 'Content-type' => 'application/pdf', | ||
274 | 'Content-Disposition' => 'attachment; filename="'.$this->title.'.pdf"', | ||
275 | 'Content-Transfer-Encoding' => 'binary', | ||
276 | ) | ||
277 | )->send(); | ||
241 | } | 278 | } |
242 | 279 | ||
280 | /** | ||
281 | * Inspired from CsvFileDumper. | ||
282 | */ | ||
243 | private function produceCSV() | 283 | private function produceCSV() |
244 | { | 284 | { |
245 | header('Content-type: application/csv'); | 285 | $delimiter = ';'; |
246 | header('Content-Disposition: attachment; filename="'.$this->title.'.csv"'); | 286 | $enclosure = '"'; |
247 | header('Content-Transfer-Encoding: UTF-8'); | 287 | $handle = fopen('php://memory', 'rb+'); |
248 | 288 | ||
249 | $output = fopen('php://output', 'a'); | 289 | fputcsv($handle, array('Title', 'URL', 'Content', 'Tags', 'MIME Type', 'Language'), $delimiter, $enclosure); |
250 | 290 | ||
251 | fputcsv($output, array('Title', 'URL', 'Content', 'Tags', 'MIME Type', 'Language')); | ||
252 | foreach ($this->entries as $entry) { | 291 | foreach ($this->entries as $entry) { |
253 | fputcsv($output, array($entry->getTitle(), | 292 | fputcsv( |
254 | $entry->getURL(), | 293 | $handle, |
255 | $entry->getContent(), | 294 | array( |
256 | implode(', ', $entry->getTags()->toArray()), | 295 | $entry->getTitle(), |
257 | $entry->getMimetype(), | 296 | $entry->getURL(), |
258 | $entry->getLanguage(), )); | 297 | $entry->getContent(), |
298 | implode(', ', $entry->getTags()->toArray()), | ||
299 | $entry->getMimetype(), | ||
300 | $entry->getLanguage(), | ||
301 | ), | ||
302 | $delimiter, | ||
303 | $enclosure | ||
304 | ); | ||
305 | } | ||
306 | |||
307 | rewind($handle); | ||
308 | $output = stream_get_contents($handle); | ||
309 | fclose($handle); | ||
310 | |||
311 | return Response::create( | ||
312 | $output, | ||
313 | 200, | ||
314 | array( | ||
315 | 'Content-type' => 'application/csv', | ||
316 | 'Content-Disposition' => 'attachment; filename="'.$this->title.'.csv"', | ||
317 | 'Content-Transfer-Encoding' => 'UTF-8', | ||
318 | ) | ||
319 | )->send(); | ||
320 | } | ||
321 | |||
322 | /** | ||
323 | * Return a kind of footer / information for the epub. | ||
324 | * | ||
325 | * @param string $type Generator of the export, can be: tdpdf, PHPePub, PHPMobi | ||
326 | * | ||
327 | * @return string | ||
328 | */ | ||
329 | private function getExportInformation($type) | ||
330 | { | ||
331 | $info = str_replace('%EXPORT_METHOD%', $type, $this->footerTemplate); | ||
332 | |||
333 | if ('tcpdf' === $type) { | ||
334 | return str_replace('%IMAGE%', '<img src="'.$this->logoPath.'" />', $info); | ||
259 | } | 335 | } |
260 | fclose($output); | 336 | |
261 | exit(); | 337 | return str_replace('%IMAGE%', '', $info); |
262 | } | 338 | } |
263 | } | 339 | } |
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/Tests/Controller/ExportControllerTest.php b/src/Wallabag/CoreBundle/Tests/Controller/ExportControllerTest.php new file mode 100644 index 00000000..19d5f01d --- /dev/null +++ b/src/Wallabag/CoreBundle/Tests/Controller/ExportControllerTest.php | |||
@@ -0,0 +1,116 @@ | |||
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 | $crawler = $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 | $crawler = $client->request('GET', '/export/unread.xslx'); | ||
35 | |||
36 | $this->assertEquals(404, $client->getResponse()->getStatusCode()); | ||
37 | } | ||
38 | |||
39 | public function testEpubExport() | ||
40 | { | ||
41 | $this->logInAs('admin'); | ||
42 | $client = $this->getClient(); | ||
43 | |||
44 | ob_start(); | ||
45 | $crawler = $client->request('GET', '/export/archive.epub'); | ||
46 | ob_end_clean(); | ||
47 | |||
48 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); | ||
49 | |||
50 | $headers = $client->getResponse()->headers; | ||
51 | $this->assertEquals('application/epub+zip', $headers->get('content-type')); | ||
52 | $this->assertEquals('attachment; filename="Archive articles.epub"', $headers->get('content-disposition')); | ||
53 | $this->assertEquals('binary', $headers->get('content-transfer-encoding')); | ||
54 | } | ||
55 | |||
56 | public function testMobiExport() | ||
57 | { | ||
58 | $this->logInAs('admin'); | ||
59 | $client = $this->getClient(); | ||
60 | |||
61 | $content = $client->getContainer() | ||
62 | ->get('doctrine.orm.entity_manager') | ||
63 | ->getRepository('WallabagCoreBundle:Entry') | ||
64 | ->findOneByUsernameAndNotArchived('admin'); | ||
65 | |||
66 | ob_start(); | ||
67 | $crawler = $client->request('GET', '/export/'.$content->getId().'.mobi'); | ||
68 | ob_end_clean(); | ||
69 | |||
70 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); | ||
71 | |||
72 | $headers = $client->getResponse()->headers; | ||
73 | $this->assertEquals('application/x-mobipocket-ebook', $headers->get('content-type')); | ||
74 | $this->assertEquals('attachment; filename="testtitleentry1.mobi"', $headers->get('content-disposition')); | ||
75 | $this->assertEquals('binary', $headers->get('content-transfer-encoding')); | ||
76 | } | ||
77 | |||
78 | public function testPdfExport() | ||
79 | { | ||
80 | $this->logInAs('admin'); | ||
81 | $client = $this->getClient(); | ||
82 | |||
83 | ob_start(); | ||
84 | $crawler = $client->request('GET', '/export/all.pdf'); | ||
85 | ob_end_clean(); | ||
86 | |||
87 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); | ||
88 | |||
89 | $headers = $client->getResponse()->headers; | ||
90 | $this->assertEquals('application/pdf', $headers->get('content-type')); | ||
91 | $this->assertEquals('attachment; filename="All articles.pdf"', $headers->get('content-disposition')); | ||
92 | $this->assertEquals('binary', $headers->get('content-transfer-encoding')); | ||
93 | } | ||
94 | |||
95 | public function testCsvExport() | ||
96 | { | ||
97 | $this->logInAs('admin'); | ||
98 | $client = $this->getClient(); | ||
99 | |||
100 | ob_start(); | ||
101 | $crawler = $client->request('GET', '/export/unread.csv'); | ||
102 | ob_end_clean(); | ||
103 | |||
104 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); | ||
105 | |||
106 | $headers = $client->getResponse()->headers; | ||
107 | $this->assertEquals('application/csv', $headers->get('content-type')); | ||
108 | $this->assertEquals('attachment; filename="Unread articles.csv"', $headers->get('content-disposition')); | ||
109 | $this->assertEquals('UTF-8', $headers->get('content-transfer-encoding')); | ||
110 | |||
111 | $csv = str_getcsv($client->getResponse()->getContent(), "\n"); | ||
112 | |||
113 | $this->assertGreaterThan(1, $csv); | ||
114 | $this->assertEquals('Title;URL;Content;Tags;"MIME Type";Language', $csv[0]); | ||
115 | } | ||
116 | } | ||