diff --git a/composer.json b/composer.json index a46e990ad..a25e143ae 100644 --- a/composer.json +++ b/composer.json @@ -55,7 +55,9 @@ "j0k3r/graby": "~1.0", "friendsofsymfony/user-bundle": "dev-master", "friendsofsymfony/oauth-server-bundle": "^1.4@dev", - "scheb/two-factor-bundle": "~1.4" + "scheb/two-factor-bundle": "~1.4", + "grandt/phpepub": "~4.0", + "wallabag/phpMobi": "~1.0.0" }, "require-dev": { "doctrine/doctrine-fixtures-bundle": "~2.2.0", @@ -63,6 +65,12 @@ "phpunit/phpunit": "~4.4", "symfony/phpunit-bridge": "~2.7.0" }, + "repositories": [ + { + "type": "vcs", + "url": "https://github.com/wallabag/phpMobi" + } + ], "scripts": { "post-install-cmd": [ "Incenteev\\ParameterHandler\\ScriptHandler::buildParameters", diff --git a/composer.lock b/composer.lock index ec11324f5..c634abbdd 100644 --- a/composer.lock +++ b/composer.lock @@ -1088,6 +1088,244 @@ ], "time": "2015-11-03 10:24:23" }, + { + "name": "grandt/binstring", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/Grandt/PHPBinString.git", + "reference": "825fe2ac8a68190f651fc2dbc07b6edde18bc431" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Grandt/PHPBinString/zipball/825fe2ac8a68190f651fc2dbc07b6edde18bc431", + "reference": "825fe2ac8a68190f651fc2dbc07b6edde18bc431", + "shasum": "" + }, + "require": { + "php": ">=5.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "BinString.php", + "BinStringStatic.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1" + ], + "authors": [ + { + "name": "A. Grandt", + "email": "php@grandt.com", + "role": "Developer" + } + ], + "description": "A class for working around the use of mbstring.func_override", + "homepage": "https://github.com/Grandt/PHPBinString", + "keywords": [ + "binary strings", + "mbstring" + ], + "time": "2015-08-13 06:14:41" + }, + { + "name": "grandt/phpepub", + "version": "4.0.3", + "source": { + "type": "git", + "url": "https://github.com/Grandt/PHPePub.git", + "reference": "dee0c5549a8d2c6bf6a1ad5b4ee21d245b711fca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Grandt/PHPePub/zipball/dee0c5549a8d2c6bf6a1ad5b4ee21d245b711fca", + "reference": "dee0c5549a8d2c6bf6a1ad5b4ee21d245b711fca", + "shasum": "" + }, + "require": { + "grandt/phpresizegif": ">=1.0.3", + "grandt/relativepath": ">=1.0.1", + "php": ">=5.3.0", + "phpzip/phpzip": ">=2.0.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPePub\\": "src/PHPePub" + }, + "classmap": [ + "src/lib.uuid.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1" + ], + "authors": [ + { + "name": "A. Grandt", + "email": "php@grandt.com", + "homepage": "http://grandt.com", + "role": "Developer" + } + ], + "description": "Package to create and stream e-books in the ePub 2.0 and 3.0 formats.", + "homepage": "https://github.com/Grandt/PHPZip", + "keywords": [ + "e-book", + "epub" + ], + "time": "2015-09-15 08:47:09" + }, + { + "name": "grandt/phpresizegif", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/Grandt/PHPResizeGif.git", + "reference": "775f6810fcda2fd1d8ca881d44a80c8d310ae7fe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Grandt/PHPResizeGif/zipball/775f6810fcda2fd1d8ca881d44a80c8d310ae7fe", + "reference": "775f6810fcda2fd1d8ca881d44a80c8d310ae7fe", + "shasum": "" + }, + "require": { + "grandt/binstring": ">=0.2.0", + "php": ">=5.3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "grandt\\ResizeGif\\": "src/ResizeGif", + "grandt\\ResizeGif\\Files\\": "src/ResizeGif/Files", + "grandt\\ResizeGif\\Structure\\": "src/ResizeGif/Structure", + "grandt\\ResizeGif\\Debug\\": "src/ResizeGif/Debug" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1" + ], + "authors": [ + { + "name": "A. Grandt", + "email": "php@grandt.com", + "homepage": "http://grandt.com", + "role": "Developer" + } + ], + "description": "GIF89a compliant Gif resizer, including transparency and optimized gifs with sub sized elements.", + "homepage": "https://github.com/Grandt/PHPResizeGif", + "keywords": [ + "GIF89a", + "animated gif", + "gif", + "resize" + ], + "time": "2015-05-10 10:52:24" + }, + { + "name": "grandt/phpzipmerge", + "version": "1.0.4", + "source": { + "type": "git", + "url": "https://github.com/Grandt/PHPZipMerge.git", + "reference": "0b1273d3c2dbfe244904158b1dbd65a663264fb9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Grandt/PHPZipMerge/zipball/0b1273d3c2dbfe244904158b1dbd65a663264fb9", + "reference": "0b1273d3c2dbfe244904158b1dbd65a663264fb9", + "shasum": "" + }, + "require": { + "grandt/binstring": ">=1.0.0", + "grandt/relativepath": ">=1.0.1", + "php": ">=5.3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "ZipMerge\\": "src/ZipMerge" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1" + ], + "authors": [ + { + "name": "A. Grandt", + "email": "php@grandt.com", + "homepage": "http://grandt.com", + "role": "Developer" + }, + { + "name": "Greg Kappatos", + "homepage": "http://websiteconnect.com.au", + "role": "Developer" + } + ], + "description": "Merge and stream multiple Zip files on the fly.", + "homepage": "https://github.com/Grandt/PHPZipMerge", + "keywords": [ + "archive", + "compressed", + "compression", + "merge", + "phpzip", + "pkzip", + "stream", + "zip" + ], + "time": "2015-08-18 13:49:33" + }, + { + "name": "grandt/relativepath", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/Grandt/PHPRelativePath.git", + "reference": "19541133c24143b6295688472c54dd6ed15a5462" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Grandt/PHPRelativePath/zipball/19541133c24143b6295688472c54dd6ed15a5462", + "reference": "19541133c24143b6295688472c54dd6ed15a5462", + "shasum": "" + }, + "require": { + "php": ">=5.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "RelativePath.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1" + ], + "authors": [ + { + "name": "A. Grandt", + "email": "php@grandt.com", + "role": "Developer" + } + ], + "description": "A class for cleaning up/collapsing relative paths. Like real_path, but without the need for the path to exist on the filesystem.", + "homepage": "https://github.com/Grandt/PHPRelativePath", + "keywords": [ + "file path" + ], + "time": "2015-05-14 08:18:23" + }, { "name": "guzzlehttp/guzzle", "version": "5.3.0", @@ -1394,20 +1632,20 @@ } ], "description": "Graby helps you extract article content from web pages", - "time": "2015-10-28 06:38:06" + "time": "2015-10-14 17:55:10" }, { "name": "j0k3r/graby-site-config", - "version": "1.0.6", + "version": "1.0.5", "source": { "type": "git", "url": "https://github.com/j0k3r/graby-site-config.git", - "reference": "6631451c1be563ff3bc8fa16c60ef7df38f8e65b" + "reference": "44afb3d6a90a816752426537e92e85160bdb40b9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/j0k3r/graby-site-config/zipball/6631451c1be563ff3bc8fa16c60ef7df38f8e65b", - "reference": "6631451c1be563ff3bc8fa16c60ef7df38f8e65b", + "url": "https://api.github.com/repos/j0k3r/graby-site-config/zipball/44afb3d6a90a816752426537e92e85160bdb40b9", + "reference": "44afb3d6a90a816752426537e92e85160bdb40b9", "shasum": "" }, "require": { @@ -1900,16 +2138,16 @@ }, { "name": "lexik/form-filter-bundle", - "version": "v4.0.2", + "version": "v4.0.1", "source": { "type": "git", "url": "https://github.com/lexik/LexikFormFilterBundle.git", - "reference": "d6081d308b71e14509602722c78d28801e4ed78a" + "reference": "8e54a41376eab7890d4deea4088ad5c86df964ee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/lexik/LexikFormFilterBundle/zipball/d6081d308b71e14509602722c78d28801e4ed78a", - "reference": "d6081d308b71e14509602722c78d28801e4ed78a", + "url": "https://api.github.com/repos/lexik/LexikFormFilterBundle/zipball/8e54a41376eab7890d4deea4088ad5c86df964ee", + "reference": "8e54a41376eab7890d4deea4088ad5c86df964ee", "shasum": "" }, "require": { @@ -2523,6 +2761,67 @@ ], "time": "2015-07-25 16:39:46" }, + { + "name": "phpzip/phpzip", + "version": "2.0.7", + "source": { + "type": "git", + "url": "https://github.com/Grandt/PHPZip.git", + "reference": "a43a7ce8b2f21050f8b143876c5c1661b0d65306" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Grandt/PHPZip/zipball/a43a7ce8b2f21050f8b143876c5c1661b0d65306", + "reference": "a43a7ce8b2f21050f8b143876c5c1661b0d65306", + "shasum": "" + }, + "require": { + "grandt/binstring": ">=0.2.0", + "grandt/phpzipmerge": ">=1.0.3", + "grandt/relativepath": ">=1.0.1", + "php": ">=5.3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPZip\\Zip\\": "src/Zip" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1" + ], + "authors": [ + { + "name": "Adam Schmalhofer", + "email": "Adam.Schmalhofer@gmx.de", + "role": "Developer" + }, + { + "name": "A. Grandt", + "email": "php@grandt.com", + "homepage": "http://grandt.com", + "role": "Developer" + }, + { + "name": "Greg Kappatos", + "homepage": "http://websiteconnect.com.au", + "role": "Developer" + } + ], + "description": "Package to create and stream archives of compressed files in ZIP format with PHP 5.3+", + "homepage": "https://github.com/Grandt/PHPZip", + "keywords": [ + "archive", + "compressed", + "compression", + "phpzip", + "pkzip", + "stream", + "zip" + ], + "time": "2015-04-30 06:45:53" + }, { "name": "psr/log", "version": "1.0.0", @@ -3496,6 +3795,43 @@ ], "time": "2015-11-05 12:49:06" }, + { + "name": "wallabag/phpMobi", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/wallabag/phpMobi.git", + "reference": "5137696542f08f8e6a0603c01970c6d3eca9873d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wallabag/phpMobi/zipball/5137696542f08f8e6a0603c01970c6d3eca9873d", + "reference": "5137696542f08f8e6a0603c01970c6d3eca9873d", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "autoload": { + "files": [ + "MOBIClass/MOBI.php" + ] + }, + "authors": [ + { + "name": "Nicolas LÅ“uillet", + "email": "nicolas@loeuillet.org", + "homepage": "http://www.cdetc.fr" + } + ], + "description": "An experimental Mobipocket file creator in PHP.", + "homepage": "https://github.com/wallabag/phpMobi", + "support": { + "source": "https://github.com/wallabag/phpMobi/tree/1.0.0" + }, + "time": "2015-01-19 12:43:17" + }, { "name": "willdurand/hateoas", "version": "v2.6.0", diff --git a/src/Wallabag/CoreBundle/Controller/ExportController.php b/src/Wallabag/CoreBundle/Controller/ExportController.php new file mode 100644 index 000000000..123e491ab --- /dev/null +++ b/src/Wallabag/CoreBundle/Controller/ExportController.php @@ -0,0 +1,65 @@ +getDoctrine()->getRepository('WallabagCoreBundle:Entry'); + switch ($category) { + case 'all': + $method = 'All'; + break; + + case 'unread': + $method = 'Unread'; + break; + + case 'starred': + $method = 'Starred'; + break; + + case 'archive': + $method = 'Archive'; + break; + + default: + break; + } + + $methodBuilder = 'getBuilderFor'.$method.'ByUser'; + $qb = $repository->$methodBuilder($this->getUser()->getId()); + $entries = $qb->getQuery()->getResult(); + + $export = new EntriesExport($entries); + $export->setMethod($method); + $export->exportAs($format); + } + + /** + * Gets one entry content. + * + * @param Entry $entry + * + * @Route("/export/id/{id}.{format}", requirements={"id" = "\d+"}, name="ebook_entry") + */ + public function getEntryAction(Entry $entry, $format) + { + $export = new EntriesExport(array($entry)); + $export->setMethod('entry'); + $export->exportAs($format); + } +} diff --git a/src/Wallabag/CoreBundle/Helper/EntriesExport.php b/src/Wallabag/CoreBundle/Helper/EntriesExport.php new file mode 100644 index 000000000..fad0bb977 --- /dev/null +++ b/src/Wallabag/CoreBundle/Helper/EntriesExport.php @@ -0,0 +1,263 @@ +entries = $entries; + + foreach ($entries as $entry) { + $this->tags[] = $entry->getTags(); + } + if (count($entries) === 1) { + $this->language = $entries[0]->getLanguage(); + } + } + + /** + * Sets the category of which we want to get articles, or just one entry. + * + * @param string $method Method to get articles + */ + public function setMethod($method) + { + $this->method = $method; + + switch ($this->method) { + case 'All': + $this->title = 'All Articles'; + break; + case 'Unread': + $this->title = 'Unread articles'; + break; + case 'Starred': + $this->title = 'Starred articles'; + break; + case 'Archive': + $this->title = 'Archived articles'; + break; + case 'entry': + $this->title = $this->entries[0]->getTitle(); + break; + default: + break; + } + } + + /** + * Sets the output format. + * + * @param string $format + */ + public function exportAs($format) + { + $this->format = $format; + + switch ($this->format) { + case 'epub': + $this->produceEpub(); + break; + + case 'mobi': + $this->produceMobi(); + break; + + case 'pdf': + $this->producePDF(); + break; + + case 'csv': + $this->produceCSV(); + break; + + default: + break; + } + } + + private function produceEpub() + { + /* + * Start and End of the book + */ + $content_start = + "\n" + ."\n" + .'' + ."\n" + .''._('wallabag articles book')."\n" + ."\n" + ."\n"; + + $bookEnd = "\n\n"; + + $book = new EPub(EPub::BOOK_VERSION_EPUB3); + + /* + * Book metadata + */ + + $book->setTitle($this->title); + $book->setIdentifier($this->title, EPub::IDENTIFIER_URI); // Could also be the ISBN number, prefered for published books, or a UUID. + $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. + $book->setDescription(_('Some articles saved on my wallabag')); + + foreach ($this->authors as $author) { + $book->setAuthor($author, $author); + } + + $book->setPublisher('wallabag', 'wallabag'); // I hope this is a non existant address :) + $book->setDate(time()); // Strictly not needed as the book date defaults to time(). + $book->setSourceURL("http://$_SERVER[HTTP_HOST]"); + + $book->addDublinCoreMetadata(DublinCore::CONTRIBUTOR, 'PHP'); + $book->addDublinCoreMetadata(DublinCore::CONTRIBUTOR, 'wallabag'); + + /* + * Front page + */ + + $book->setCoverImage('Cover.png', file_get_contents('themes/_global/img/appicon/apple-touch-icon-152.png'), 'image/png'); + + $cover = $content_start.'

'._('Produced by wallabag with PHPePub').'

'._('Please open an issue if you have trouble with the display of this E-Book on your device.').'

'.$bookEnd; + + $book->addChapter('Notices', 'Cover2.html', $cover); + + $book->buildTOC(); + + /* + * Adding actual entries + */ + + foreach ($this->entries as $entry) { //set tags as subjects + foreach ($this->tags as $tag) { + $book->setSubject($tag['value']); + } + + $chapter = $content_start.$entry->getContent().$bookEnd; + $book->addChapter($entry->getTitle(), htmlspecialchars($entry->getTitle()).'.html', $chapter, true, EPub::EXTERNAL_REF_ADD); + } + $book->finalize(); + $book->sendBook($this->title); + } + + private function produceMobi() + { + $mobi = new \MOBI(); + $content = new \MOBIFile(); + + /* + * Book metadata + */ + + $content->set('title', $this->title); + $content->set('author', implode($this->authors)); + $content->set('subject', $this->title); + + /* + * Front page + */ + + $content->appendParagraph('

'._('Produced by wallabag with PHPMobi').'

'._('Please open an issue if you have trouble with the display of this E-Book on your device.').'

'); + $content->appendImage(imagecreatefrompng('themes/_global/img/appicon/apple-touch-icon-152.png')); + $content->appendPageBreak(); + + /* + * Adding actual entries + */ + + foreach ($this->entries as $entry) { + $content->appendChapterTitle($entry->getTitle()); + $content->appendParagraph($entry->getContent()); + $content->appendPageBreak(); + } + $mobi->setContentProvider($content); + + // the browser inside Kindle Devices doesn't likes special caracters either, we limit to A-z/0-9 + $this->title = preg_replace('/[^A-Za-z0-9\-]/', '', $this->title); + + // we offer file to download + $mobi->download($this->title.'.mobi'); + } + + private function producePDF() + { + $pdf = new \TCPDF(PDF_PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT, true, 'UTF-8', false); + + /* + * Book metadata + */ + + $pdf->SetCreator(PDF_CREATOR); + $pdf->SetAuthor('wallabag'); + $pdf->SetTitle($this->title); + $pdf->SetSubject('Articles via wallabag'); + $pdf->SetKeywords('wallabag'); + + /* + * Front page + */ + + $pdf->AddPage(); + $intro = '

'.$this->title.'

+

'._('Produced by wallabag with tcpdf').'

+

'._('Please open an issue if you have trouble with the display of this E-Book on your device.').'

+
'; + + $pdf->writeHTMLCell(0, 0, '', '', $intro, 0, 1, 0, true, '', true); + + /* + * Adding actual entries + */ + + foreach ($this->entries as $entry) { + foreach ($this->tags as $tag) { + $pdf->SetKeywords($tag['value']); + } + + $pdf->AddPage(); + $html = '

'.$entry->getTitle().'

'; + $html .= $entry->getContent(); + $pdf->writeHTMLCell(0, 0, '', '', $html, 0, 1, 0, true, '', true); + } + + // set image scale factor + $pdf->setImageScale(PDF_IMAGE_SCALE_RATIO); + + $pdf->Output($this->title.'.pdf', 'D'); + } + + private function produceCSV() + { + header('Content-type: application/csv'); + header('Content-Disposition: attachment; filename="'.$this->title.'.csv"'); + header('Content-Transfer-Encoding: UTF-8'); + + $output = fopen('php://output', 'a'); + + fputcsv($output, array('Title', 'URL', 'Content', 'Tags', 'MIME Type', 'Language')); + foreach ($this->entries as $entry) { + fputcsv($output, array($entry->getTitle(), + $entry->getURL(), + $entry->getContent(), + implode(', ', $entry->getTags()->toArray()), + $entry->getMimetype(), + $entry->getLanguage(), )); + } + fclose($output); + exit(); + } +} 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 668824bc0..5a231c865 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 @@ {% endfor %} + +
+ {% set currentRoute = app.request.attributes.get('_route') %} + {% if currentRoute == 'homepage' %} + {% set currentRoute = 'unread' %} + {% endif %} +

{% trans %}Export{% endtrans %}

+ +
+
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 7230506c3..bece099a5 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 @@
  • - {% trans %}Download{% endtrans %} + {% trans %}Download{% endtrans %}
      - {% if export_epub %}
    • EPUB
    • {% endif %} - {% if export_mobi %}
    • MOBI
    • {% endif %} - {% if export_pdf %}
    • PDF
    • {% endif %} + {% if export_epub %}
    • EPUB
    • {% endif %} + {% if export_mobi %}
    • MOBI
    • {% endif %} + {% if export_pdf %}
    • PDF
    • {% endif %} +
    • CSV
  • 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 95b3977cf..f426e25b9 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 @@
  • +
  • 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 edfdee82a..491a7916d 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() { } } +function init_export() { + // no display if export not aviable + if ($("div").is("#export")) { + $('#button_export').show(); + $('.button-collapse-right').sideNav({ edge: 'right' }); + } +} + $(document).ready(function(){ // sideNav $('.button-collapse').sideNav(); @@ -26,6 +34,7 @@ $(document).ready(function(){ format: 'dd/mm/yyyy', }); init_filters(); + init_export(); $('#nav-btn-add-tag').on('click', function(){ $(".nav-panel-add-tag").toggle(100);