diff options
Diffstat (limited to 'src')
6 files changed, 361 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..123e491a --- /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 Wallabag\CoreBundle\Entity\Entry; | ||
8 | use Wallabag\CoreBundle\Helper\EntriesExport; | ||
9 | |||
10 | class ExportController extends Controller | ||
11 | { | ||
12 | /** | ||
13 | * Gets all entries for current user. | ||
14 | * | ||
15 | * @Route("/export/{category}.{format}", name="ebook", requirements={ | ||
16 | * "_format": "epub|mobi|pdf|json|xml|txt|csv" | ||
17 | * }) | ||
18 | */ | ||
19 | public function getEntriesAction($format, $category) | ||
20 | { | ||
21 | $repository = $this->getDoctrine()->getRepository('WallabagCoreBundle:Entry'); | ||
22 | switch ($category) { | ||
23 | case 'all': | ||
24 | $method = 'All'; | ||
25 | break; | ||
26 | |||
27 | case 'unread': | ||
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 | } | ||
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 | } | ||
51 | |||
52 | /** | ||
53 | * Gets one entry content. | ||
54 | * | ||
55 | * @param Entry $entry | ||
56 | * | ||
57 | * @Route("/export/id/{id}.{format}", requirements={"id" = "\d+"}, name="ebook_entry") | ||
58 | */ | ||
59 | public function getEntryAction(Entry $entry, $format) | ||
60 | { | ||
61 | $export = new EntriesExport(array($entry)); | ||
62 | $export->setMethod('entry'); | ||
63 | $export->exportAs($format); | ||
64 | } | ||
65 | } | ||
diff --git a/src/Wallabag/CoreBundle/Helper/EntriesExport.php b/src/Wallabag/CoreBundle/Helper/EntriesExport.php new file mode 100644 index 00000000..fad0bb97 --- /dev/null +++ b/src/Wallabag/CoreBundle/Helper/EntriesExport.php | |||
@@ -0,0 +1,263 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Wallabag\CoreBundle\Helper; | ||
4 | |||
5 | use PHPePub\Core\EPub; | ||
6 | use PHPePub\Core\Structure\OPF\DublinCore; | ||
7 | |||
8 | class EntriesExport | ||
9 | { | ||
10 | private $format; | ||
11 | private $method; | ||
12 | private $title; | ||
13 | private $entries; | ||
14 | private $authors = array('wallabag'); | ||
15 | private $language; | ||
16 | private $tags; | ||
17 | |||
18 | public function __construct($entries) | ||
19 | { | ||
20 | $this->entries = $entries; | ||
21 | |||
22 | foreach ($entries as $entry) { | ||
23 | $this->tags[] = $entry->getTags(); | ||
24 | } | ||
25 | if (count($entries) === 1) { | ||
26 | $this->language = $entries[0]->getLanguage(); | ||
27 | } | ||
28 | } | ||
29 | |||
30 | /** | ||
31 | * Sets the category of which we want to get articles, or just one entry. | ||
32 | * | ||
33 | * @param string $method Method to get articles | ||
34 | */ | ||
35 | public function setMethod($method) | ||
36 | { | ||
37 | $this->method = $method; | ||
38 | |||
39 | switch ($this->method) { | ||
40 | case 'All': | ||
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 | } | ||
58 | } | ||
59 | |||
60 | /** | ||
61 | * Sets the output format. | ||
62 | * | ||
63 | * @param string $format | ||
64 | */ | ||
65 | public function exportAs($format) | ||
66 | { | ||
67 | $this->format = $format; | ||
68 | |||
69 | switch ($this->format) { | ||
70 | case 'epub': | ||
71 | $this->produceEpub(); | ||
72 | break; | ||
73 | |||
74 | case 'mobi': | ||
75 | $this->produceMobi(); | ||
76 | break; | ||
77 | |||
78 | case 'pdf': | ||
79 | $this->producePDF(); | ||
80 | break; | ||
81 | |||
82 | case 'csv': | ||
83 | $this->produceCSV(); | ||
84 | break; | ||
85 | |||
86 | default: | ||
87 | break; | ||
88 | } | ||
89 | } | ||
90 | |||
91 | private function produceEpub() | ||
92 | { | ||
93 | /* | ||
94 | * Start and End of the book | ||
95 | */ | ||
96 | $content_start = | ||
97 | "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" | ||
98 | ."<html xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:epub=\"http://www.idpf.org/2007/ops\">\n" | ||
99 | .'<head>' | ||
100 | ."<meta http-equiv=\"Default-Style\" content=\"text/html; charset=utf-8\" />\n" | ||
101 | .'<title>'._('wallabag articles book')."</title>\n" | ||
102 | ."</head>\n" | ||
103 | ."<body>\n"; | ||
104 | |||
105 | $bookEnd = "</body>\n</html>\n"; | ||
106 | |||
107 | $book = new EPub(EPub::BOOK_VERSION_EPUB3); | ||
108 | |||
109 | /* | ||
110 | * Book metadata | ||
111 | */ | ||
112 | |||
113 | $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. | ||
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. | ||
116 | $book->setDescription(_('Some articles saved on my wallabag')); | ||
117 | |||
118 | foreach ($this->authors as $author) { | ||
119 | $book->setAuthor($author, $author); | ||
120 | } | ||
121 | |||
122 | $book->setPublisher('wallabag', 'wallabag'); // I hope this is a non existant address :) | ||
123 | $book->setDate(time()); // Strictly not needed as the book date defaults to time(). | ||
124 | $book->setSourceURL("http://$_SERVER[HTTP_HOST]"); | ||
125 | |||
126 | $book->addDublinCoreMetadata(DublinCore::CONTRIBUTOR, 'PHP'); | ||
127 | $book->addDublinCoreMetadata(DublinCore::CONTRIBUTOR, 'wallabag'); | ||
128 | |||
129 | /* | ||
130 | * Front page | ||
131 | */ | ||
132 | |||
133 | $book->setCoverImage('Cover.png', file_get_contents('themes/_global/img/appicon/apple-touch-icon-152.png'), 'image/png'); | ||
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 | |||
139 | $book->buildTOC(); | ||
140 | |||
141 | /* | ||
142 | * Adding actual entries | ||
143 | */ | ||
144 | |||
145 | foreach ($this->entries as $entry) { //set tags as subjects | ||
146 | foreach ($this->tags as $tag) { | ||
147 | $book->setSubject($tag['value']); | ||
148 | } | ||
149 | |||
150 | $chapter = $content_start.$entry->getContent().$bookEnd; | ||
151 | $book->addChapter($entry->getTitle(), htmlspecialchars($entry->getTitle()).'.html', $chapter, true, EPub::EXTERNAL_REF_ADD); | ||
152 | } | ||
153 | $book->finalize(); | ||
154 | $book->sendBook($this->title); | ||
155 | } | ||
156 | |||
157 | private function produceMobi() | ||
158 | { | ||
159 | $mobi = new \MOBI(); | ||
160 | $content = new \MOBIFile(); | ||
161 | |||
162 | /* | ||
163 | * Book metadata | ||
164 | */ | ||
165 | |||
166 | $content->set('title', $this->title); | ||
167 | $content->set('author', implode($this->authors)); | ||
168 | $content->set('subject', $this->title); | ||
169 | |||
170 | /* | ||
171 | * Front page | ||
172 | */ | ||
173 | |||
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>'); | ||
175 | $content->appendImage(imagecreatefrompng('themes/_global/img/appicon/apple-touch-icon-152.png')); | ||
176 | $content->appendPageBreak(); | ||
177 | |||
178 | /* | ||
179 | * Adding actual entries | ||
180 | */ | ||
181 | |||
182 | foreach ($this->entries as $entry) { | ||
183 | $content->appendChapterTitle($entry->getTitle()); | ||
184 | $content->appendParagraph($entry->getContent()); | ||
185 | $content->appendPageBreak(); | ||
186 | } | ||
187 | $mobi->setContentProvider($content); | ||
188 | |||
189 | // 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); | ||
191 | |||
192 | // we offer file to download | ||
193 | $mobi->download($this->title.'.mobi'); | ||
194 | } | ||
195 | |||
196 | private function producePDF() | ||
197 | { | ||
198 | $pdf = new \TCPDF(PDF_PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT, true, 'UTF-8', false); | ||
199 | |||
200 | /* | ||
201 | * Book metadata | ||
202 | */ | ||
203 | |||
204 | $pdf->SetCreator(PDF_CREATOR); | ||
205 | $pdf->SetAuthor('wallabag'); | ||
206 | $pdf->SetTitle($this->title); | ||
207 | $pdf->SetSubject('Articles via wallabag'); | ||
208 | $pdf->SetKeywords('wallabag'); | ||
209 | |||
210 | /* | ||
211 | * Front page | ||
212 | */ | ||
213 | |||
214 | $pdf->AddPage(); | ||
215 | $intro = '<h1>'.$this->title.'</h1><div style="text-align:center;" > | ||
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 | |||
220 | $pdf->writeHTMLCell(0, 0, '', '', $intro, 0, 1, 0, true, '', true); | ||
221 | |||
222 | /* | ||
223 | * Adding actual entries | ||
224 | */ | ||
225 | |||
226 | foreach ($this->entries as $entry) { | ||
227 | foreach ($this->tags as $tag) { | ||
228 | $pdf->SetKeywords($tag['value']); | ||
229 | } | ||
230 | |||
231 | $pdf->AddPage(); | ||
232 | $html = '<h1>'.$entry->getTitle().'</h1>'; | ||
233 | $html .= $entry->getContent(); | ||
234 | $pdf->writeHTMLCell(0, 0, '', '', $html, 0, 1, 0, true, '', true); | ||
235 | } | ||
236 | |||
237 | // set image scale factor | ||
238 | $pdf->setImageScale(PDF_IMAGE_SCALE_RATIO); | ||
239 | |||
240 | $pdf->Output($this->title.'.pdf', 'D'); | ||
241 | } | ||
242 | |||
243 | private function produceCSV() | ||
244 | { | ||
245 | header('Content-type: application/csv'); | ||
246 | header('Content-Disposition: attachment; filename="'.$this->title.'.csv"'); | ||
247 | header('Content-Transfer-Encoding: UTF-8'); | ||
248 | |||
249 | $output = fopen('php://output', 'a'); | ||
250 | |||
251 | fputcsv($output, array('Title', 'URL', 'Content', 'Tags', 'MIME Type', 'Language')); | ||
252 | foreach ($this->entries as $entry) { | ||
253 | fputcsv($output, array($entry->getTitle(), | ||
254 | $entry->getURL(), | ||
255 | $entry->getContent(), | ||
256 | implode(', ', $entry->getTags()->toArray()), | ||
257 | $entry->getMimetype(), | ||
258 | $entry->getLanguage(), )); | ||
259 | } | ||
260 | fclose($output); | ||
261 | exit(); | ||
262 | } | ||
263 | } | ||
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..5a231c86 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('ebook', { 'category': currentRoute, 'format': 'epub' }) }}">{% trans %}EPUB{% endtrans %}</a></li> | ||
103 | <li class="bold"><a class="waves-effect" href="{{ path('ebook', { 'category': currentRoute, 'format': 'mobi' }) }}">{% trans %}MOBI{% endtrans %}</a></li> | ||
104 | <li class="bold"><a class="waves-effect" href="{{ path('ebook', { 'category': currentRoute, 'format': 'pdf' }) }}">{% trans %}PDF{% endtrans %}</a></li> | ||
105 | <li class="bold"><del><a class="waves-effect" href="{{ path('ebook', { 'category': currentRoute, 'format': 'xml' }) }}">{% trans %}XML{% endtrans %}</a></del></li> | ||
106 | <li class="bold"><del><a class="waves-effect" href="{{ path('ebook', { 'category': currentRoute, 'format': 'json' }) }}">{% trans %}JSON{% endtrans %}</a></del></li> | ||
107 | <li class="bold"><a class="waves-effect" href="{{ path('ebook', { 'category': currentRoute, 'format': 'csv' }) }}">{% trans %}CSV{% endtrans %}</a></li> | ||
108 | <li class="bold"><del><a class="waves-effect" href="{{ path('ebook', { '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..bece099a 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,14 @@ | |||
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('ebook_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('ebook_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('ebook_entry', { 'id': entry.id, 'format': 'pdf' }) }}" title="Generate PDF file">PDF</a></li>{% endif %} |
112 | <li><a href="{{ path('ebook_entry', { 'id': entry.id, 'format': 'csv' }) }}" title="Generate CSV file">CSV</a></li> | ||
112 | </ul> | 113 | </ul> |
113 | </div> | 114 | </div> |
114 | </li> | 115 | </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); |