From 03690d138792dde6405e3d2eb3c53f6572eb3c43 Mon Sep 17 00:00:00 2001 From: Thomas Citharel Date: Thu, 15 Oct 2015 20:06:59 +0200 Subject: [PATCH 01/10] Start work on export For now: - ebook - mobi - pdf - csv --- composer.json | 10 +- composer.lock | 354 +++++++++++++++++- .../Controller/ExportController.php | 65 ++++ .../CoreBundle/Helper/EntriesExport.php | 263 +++++++++++++ .../themes/material/Entry/entries.html.twig | 18 + .../themes/material/Entry/entry.html.twig | 9 +- .../views/themes/material/layout.html.twig | 1 + .../views/themes/material/public/js/init.js | 9 + 8 files changed, 715 insertions(+), 14 deletions(-) create mode 100644 src/Wallabag/CoreBundle/Controller/ExportController.php create mode 100644 src/Wallabag/CoreBundle/Helper/EntriesExport.php 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); From add597bad95b30dbecab3aecc8362a1ccd427976 Mon Sep 17 00:00:00 2001 From: Jeremy Benoist Date: Fri, 16 Oct 2015 10:51:53 +0200 Subject: [PATCH 02/10] Rework on export - all export now return a `HttpFoundation\Response` - return a 404 on unsupported format - add tests --- app/config/parameters.yml.dist | 1 + app/config/tests/parameters.yml.dist.mysql | 1 + app/config/tests/parameters.yml.dist.pgsql | 1 + app/config/tests/parameters.yml.dist.sqlite | 1 + composer.json | 2 +- composer.lock | 27 +- .../Controller/ExportController.php | 85 +++--- .../CoreBundle/Helper/EntriesExport.php | 256 ++++++++++++------ .../CoreBundle/Resources/config/services.yml | 6 + .../Tests/Controller/ExportControllerTest.php | 116 ++++++++ 10 files changed, 351 insertions(+), 145 deletions(-) create mode 100644 src/Wallabag/CoreBundle/Tests/Controller/ExportControllerTest.php diff --git a/app/config/parameters.yml.dist b/app/config/parameters.yml.dist index 52f9bccbc..b475d6370 100644 --- a/app/config/parameters.yml.dist +++ b/app/config/parameters.yml.dist @@ -51,6 +51,7 @@ parameters: export_epub: true export_mobi: true export_pdf: true + wallabag_url: http://v2.wallabag.org # default user config items_on_page: 12 diff --git a/app/config/tests/parameters.yml.dist.mysql b/app/config/tests/parameters.yml.dist.mysql index 03fdf5a60..5b29690c4 100644 --- a/app/config/tests/parameters.yml.dist.mysql +++ b/app/config/tests/parameters.yml.dist.mysql @@ -51,6 +51,7 @@ parameters: export_epub: true export_mobi: true export_pdf: true + wallabag_url: http://v2.wallabag.org # default user config items_on_page: 12 diff --git a/app/config/tests/parameters.yml.dist.pgsql b/app/config/tests/parameters.yml.dist.pgsql index 675ba6c91..efdac9617 100644 --- a/app/config/tests/parameters.yml.dist.pgsql +++ b/app/config/tests/parameters.yml.dist.pgsql @@ -51,6 +51,7 @@ parameters: export_epub: true export_mobi: true export_pdf: true + wallabag_url: http://v2.wallabag.org # default user config items_on_page: 12 diff --git a/app/config/tests/parameters.yml.dist.sqlite b/app/config/tests/parameters.yml.dist.sqlite index 258627af9..276d11471 100644 --- a/app/config/tests/parameters.yml.dist.sqlite +++ b/app/config/tests/parameters.yml.dist.sqlite @@ -51,6 +51,7 @@ parameters: export_epub: true export_mobi: true export_pdf: true + wallabag_url: http://v2.wallabag.org # default user config items_on_page: 12 diff --git a/composer.json b/composer.json index a25e143ae..b6a9c8541 100644 --- a/composer.json +++ b/composer.json @@ -57,7 +57,7 @@ "friendsofsymfony/oauth-server-bundle": "^1.4@dev", "scheb/two-factor-bundle": "~1.4", "grandt/phpepub": "~4.0", - "wallabag/phpMobi": "~1.0.0" + "wallabag/php-mobi": "~1.0.0" }, "require-dev": { "doctrine/doctrine-fixtures-bundle": "~2.2.0", diff --git a/composer.lock b/composer.lock index c634abbdd..ae53a6d61 100644 --- a/composer.lock +++ b/composer.lock @@ -3796,41 +3796,52 @@ "time": "2015-11-05 12:49:06" }, { - "name": "wallabag/phpMobi", - "version": "1.0.0", + "name": "wallabag/php-mobi", + "version": "1.0.1", "source": { "type": "git", "url": "https://github.com/wallabag/phpMobi.git", - "reference": "5137696542f08f8e6a0603c01970c6d3eca9873d" + "reference": "1cd7d022fe6be838535d6bba917d19cc48dcf487" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/wallabag/phpMobi/zipball/5137696542f08f8e6a0603c01970c6d3eca9873d", - "reference": "5137696542f08f8e6a0603c01970c6d3eca9873d", + "url": "https://api.github.com/repos/wallabag/phpMobi/zipball/1cd7d022fe6be838535d6bba917d19cc48dcf487", + "reference": "1cd7d022fe6be838535d6bba917d19cc48dcf487", "shasum": "" }, "require": { "php": ">=5.3.0" }, + "replace": { + "wallabag/phpmobi": "*" + }, "type": "library", "autoload": { "files": [ "MOBIClass/MOBI.php" ] }, + "license": [ + "Apache-2.0" + ], "authors": [ + { + "name": "Sander Kromwijk", + "email": "s.kromwijk@gmail.co", + "role": "Original developer" + }, { "name": "Nicolas LÅ“uillet", "email": "nicolas@loeuillet.org", "homepage": "http://www.cdetc.fr" } ], - "description": "An experimental Mobipocket file creator in PHP.", + "description": "A Mobipocket file (.mobi) creator in PHP.", "homepage": "https://github.com/wallabag/phpMobi", "support": { - "source": "https://github.com/wallabag/phpMobi/tree/1.0.0" + "source": "https://github.com/wallabag/phpMobi/tree/1.0.1" }, - "time": "2015-01-19 12:43:17" + "time": "2015-10-16 08:42:42" }, { "name": "willdurand/hateoas", diff --git a/src/Wallabag/CoreBundle/Controller/ExportController.php b/src/Wallabag/CoreBundle/Controller/ExportController.php index 123e491ab..dd3cb7ca5 100644 --- a/src/Wallabag/CoreBundle/Controller/ExportController.php +++ b/src/Wallabag/CoreBundle/Controller/ExportController.php @@ -4,62 +4,55 @@ namespace Wallabag\CoreBundle\Controller; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Symfony\Bundle\FrameworkBundle\Controller\Controller; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Wallabag\CoreBundle\Entity\Entry; -use Wallabag\CoreBundle\Helper\EntriesExport; class ExportController extends Controller { - /** - * Gets all entries for current user. - * - * @Route("/export/{category}.{format}", name="ebook", requirements={ - * "_format": "epub|mobi|pdf|json|xml|txt|csv" - * }) - */ - public function getEntriesAction($format, $category) - { - $repository = $this->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") + * @Route("/export/{id}.{format}", requirements={"id" = "\d+"}, name="export_entry") */ - public function getEntryAction(Entry $entry, $format) + public function downloadEntryAction(Entry $entry, $format) { - $export = new EntriesExport(array($entry)); - $export->setMethod('entry'); - $export->exportAs($format); + try { + return $this->get('wallabag_core.helper.entries_export') + ->setEntries($entry) + ->updateTitle('entry') + ->exportAs($format); + } catch (\InvalidArgumentException $e) { + throw new NotFoundHttpException($e->getMessage()); + } + } + + /** + * Export all entries for current user. + * + * @Route("/export/{category}.{format}", name="export_entries", requirements={ + * "_format": "epub|mobi|pdf|json|xml|txt|csv", + * "category": "all|unread|starred|archive" + * }) + */ + public function downloadEntriesAction($format, $category) + { + $method = ucfirst($category); + $methodBuilder = 'getBuilderFor'.$method.'ByUser'; + $entries = $this->getDoctrine() + ->getRepository('WallabagCoreBundle:Entry') + ->$methodBuilder($this->getUser()->getId()) + ->getQuery() + ->getResult(); + + try { + return $this->get('wallabag_core.helper.entries_export') + ->setEntries($entries) + ->updateTitle($method) + ->exportAs($format); + } catch (\InvalidArgumentException $e) { + throw new NotFoundHttpException($e->getMessage()); + } } } diff --git a/src/Wallabag/CoreBundle/Helper/EntriesExport.php b/src/Wallabag/CoreBundle/Helper/EntriesExport.php index fad0bb977..806319b1a 100644 --- a/src/Wallabag/CoreBundle/Helper/EntriesExport.php +++ b/src/Wallabag/CoreBundle/Helper/EntriesExport.php @@ -4,27 +4,51 @@ namespace Wallabag\CoreBundle\Helper; use PHPePub\Core\EPub; use PHPePub\Core\Structure\OPF\DublinCore; +use Symfony\Component\HttpFoundation\Response; class EntriesExport { - private $format; - private $method; - private $title; - private $entries; + private $wallabagUrl; + private $logoPath; + private $title = ''; + private $entries = array(); private $authors = array('wallabag'); - private $language; - private $tags; + private $language = ''; + private $tags = array(); + private $footerTemplate = '
    +

    Produced by wallabag with %EXPORT_METHOD%

    +

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

    + wallabagUrl = $wallabagUrl; + $this->logoPath = $logoPath; + } + + /** + * Define entries. + * + * @param array|Entry $entries An array of entries or one entry + */ + public function setEntries($entries) + { + if (!is_array($entries)) { + $this->language = $entries->getLanguage(); + $entries = array($entries); + } + $this->entries = $entries; foreach ($entries as $entry) { $this->tags[] = $entry->getTags(); } - if (count($entries) === 1) { - $this->language = $entries[0]->getLanguage(); - } + + return $this; } /** @@ -32,29 +56,15 @@ class EntriesExport * * @param string $method Method to get articles */ - public function setMethod($method) + public function updateTitle($method) { - $this->method = $method; + $this->title = $method.' articles'; - 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; + if ('entry' === $method) { + $this->title = $this->entries[0]->getTitle(); } + + return $this; } /** @@ -64,30 +74,26 @@ class EntriesExport */ public function exportAs($format) { - $this->format = $format; - - switch ($this->format) { + switch ($format) { case 'epub': - $this->produceEpub(); - break; + return $this->produceEpub(); case 'mobi': - $this->produceMobi(); - break; + return $this->produceMobi(); case 'pdf': - $this->producePDF(); - break; + return $this->producePDF(); case 'csv': - $this->produceCSV(); - break; - - default: - break; + return $this->produceCSV(); } + + throw new \InvalidArgumentException(sprintf('The format "%s" is not yet supported.', $format)); } + /** + * Use PHPePub to dump a .epub file. + */ private function produceEpub() { /* @@ -98,7 +104,7 @@ class EntriesExport ."\n" .'' ."\n" - .''._('wallabag articles book')."\n" + ."wallabag articles book\n" ."\n" ."\n"; @@ -111,17 +117,21 @@ class EntriesExport */ $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')); + // Could also be the ISBN number, prefered for published books, or a UUID. + $book->setIdentifier($this->title, EPub::IDENTIFIER_URI); + // 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->setLanguage($this->language); + $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]"); + // I hope this is a non existant address :) + $book->setPublisher('wallabag', 'wallabag'); + // Strictly not needed as the book date defaults to time(). + $book->setDate(time()); + $book->setSourceURL($this->wallabagUrl); $book->addDublinCoreMetadata(DublinCore::CONTRIBUTOR, 'PHP'); $book->addDublinCoreMetadata(DublinCore::CONTRIBUTOR, 'wallabag'); @@ -129,12 +139,11 @@ class EntriesExport /* * Front page */ + if (file_exists($this->logoPath)) { + $book->setCoverImage('Cover.png', file_get_contents($this->logoPath), 'image/png'); + } - $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->addChapter('Notices', 'Cover2.html', $content_start.$this->getExportInformation('PHPePub').$bookEnd); $book->buildTOC(); @@ -142,18 +151,31 @@ class EntriesExport * Adding actual entries */ - foreach ($this->entries as $entry) { //set tags as subjects - foreach ($this->tags as $tag) { - $book->setSubject($tag['value']); - } + // set tags as subjects + foreach ($this->entries as $entry) { + 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); + + return Response::create( + $book->getBook(), + 200, + array( + 'Content-Description' => 'File Transfer', + 'Content-type' => 'application/epub+zip', + 'Content-Disposition' => 'attachment; filename="'.$this->title.'.epub"', + 'Content-Transfer-Encoding' => 'binary', + ) + )->send(); } + /** + * Use PHPMobi to dump a .mobi file. + */ private function produceMobi() { $mobi = new \MOBI(); @@ -162,7 +184,6 @@ class EntriesExport /* * Book metadata */ - $content->set('title', $this->title); $content->set('author', implode($this->authors)); $content->set('subject', $this->title); @@ -170,15 +191,15 @@ class EntriesExport /* * 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->appendParagraph($this->getExportInformation('PHPMobi')); + if (file_exists($this->logoPath)) { + $content->appendImage(imagecreatefrompng($this->logoPath)); + } $content->appendPageBreak(); /* * Adding actual entries */ - foreach ($this->entries as $entry) { $content->appendChapterTitle($entry->getTitle()); $content->appendParagraph($entry->getContent()); @@ -189,10 +210,22 @@ class EntriesExport // 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'); + return Response::create( + $mobi->toString(), + 200, + array( + 'Accept-Ranges' => 'bytes', + 'Content-Description' => 'File Transfer', + 'Content-type' => 'application/x-mobipocket-ebook', + 'Content-Disposition' => 'attachment; filename="'.$this->title.'.mobi"', + 'Content-Transfer-Encoding' => 'binary', + ) + )->send(); } + /** + * Use TCPDF to dump a .pdf file. + */ private function producePDF() { $pdf = new \TCPDF(PDF_PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT, true, 'UTF-8', false); @@ -200,7 +233,6 @@ class EntriesExport /* * Book metadata */ - $pdf->SetCreator(PDF_CREATOR); $pdf->SetAuthor('wallabag'); $pdf->SetTitle($this->title); @@ -210,19 +242,14 @@ class EntriesExport /* * 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.').'

    -
    '; + $intro = '

    '.$this->title.'

    '.$this->getExportInformation('tcpdf'); $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']); @@ -231,33 +258,82 @@ class EntriesExport $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'); + return Response::create( + $pdf->Output('', 'S'), + 200, + array( + 'Content-Description' => 'File Transfer', + 'Content-type' => 'application/pdf', + 'Content-Disposition' => 'attachment; filename="'.$this->title.'.pdf"', + 'Content-Transfer-Encoding' => 'binary', + ) + )->send(); } + /** + * Inspired from CsvFileDumper. + */ private function produceCSV() { - header('Content-type: application/csv'); - header('Content-Disposition: attachment; filename="'.$this->title.'.csv"'); - header('Content-Transfer-Encoding: UTF-8'); + $delimiter = ';'; + $enclosure = '"'; + $handle = fopen('php://memory', 'rb+'); - $output = fopen('php://output', 'a'); + fputcsv($handle, array('Title', 'URL', 'Content', 'Tags', 'MIME Type', 'Language'), $delimiter, $enclosure); - 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(), )); + fputcsv( + $handle, + array( + $entry->getTitle(), + $entry->getURL(), + $entry->getContent(), + implode(', ', $entry->getTags()->toArray()), + $entry->getMimetype(), + $entry->getLanguage(), + ), + $delimiter, + $enclosure + ); } - fclose($output); - exit(); + + rewind($handle); + $output = stream_get_contents($handle); + fclose($handle); + + return Response::create( + $output, + 200, + array( + 'Content-type' => 'application/csv', + 'Content-Disposition' => 'attachment; filename="'.$this->title.'.csv"', + 'Content-Transfer-Encoding' => 'UTF-8', + ) + )->send(); + } + + /** + * Return a kind of footer / information for the epub. + * + * @param string $type Generator of the export, can be: tdpdf, PHPePub, PHPMobi + * + * @return string + */ + private function getExportInformation($type) + { + $info = str_replace('%EXPORT_METHOD%', $type, $this->footerTemplate); + + if ('tcpdf' === $type) { + return str_replace('%IMAGE%', '', $info); + } + + return str_replace('%IMAGE%', '', $info); } } diff --git a/src/Wallabag/CoreBundle/Resources/config/services.yml b/src/Wallabag/CoreBundle/Resources/config/services.yml index 65c2c8d85..8e21b0528 100644 --- a/src/Wallabag/CoreBundle/Resources/config/services.yml +++ b/src/Wallabag/CoreBundle/Resources/config/services.yml @@ -64,3 +64,9 @@ services: - %language% tags: - { name: kernel.event_subscriber } + + wallabag_core.helper.entries_export: + class: Wallabag\CoreBundle\Helper\EntriesExport + arguments: + - %wallabag_url% + - 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 000000000..19d5f01d9 --- /dev/null +++ b/src/Wallabag/CoreBundle/Tests/Controller/ExportControllerTest.php @@ -0,0 +1,116 @@ +getClient(); + + $client->request('GET', '/export/unread.csv'); + + $this->assertEquals(302, $client->getResponse()->getStatusCode()); + $this->assertContains('login', $client->getResponse()->headers->get('location')); + } + + public function testUnknownCategoryExport() + { + $this->logInAs('admin'); + $client = $this->getClient(); + + $crawler = $client->request('GET', '/export/awesomeness.epub'); + + $this->assertEquals(404, $client->getResponse()->getStatusCode()); + } + + public function testUnknownFormatExport() + { + $this->logInAs('admin'); + $client = $this->getClient(); + + $crawler = $client->request('GET', '/export/unread.xslx'); + + $this->assertEquals(404, $client->getResponse()->getStatusCode()); + } + + public function testEpubExport() + { + $this->logInAs('admin'); + $client = $this->getClient(); + + ob_start(); + $crawler = $client->request('GET', '/export/archive.epub'); + ob_end_clean(); + + $this->assertEquals(200, $client->getResponse()->getStatusCode()); + + $headers = $client->getResponse()->headers; + $this->assertEquals('application/epub+zip', $headers->get('content-type')); + $this->assertEquals('attachment; filename="Archive articles.epub"', $headers->get('content-disposition')); + $this->assertEquals('binary', $headers->get('content-transfer-encoding')); + } + + public function testMobiExport() + { + $this->logInAs('admin'); + $client = $this->getClient(); + + $content = $client->getContainer() + ->get('doctrine.orm.entity_manager') + ->getRepository('WallabagCoreBundle:Entry') + ->findOneByUsernameAndNotArchived('admin'); + + ob_start(); + $crawler = $client->request('GET', '/export/'.$content->getId().'.mobi'); + ob_end_clean(); + + $this->assertEquals(200, $client->getResponse()->getStatusCode()); + + $headers = $client->getResponse()->headers; + $this->assertEquals('application/x-mobipocket-ebook', $headers->get('content-type')); + $this->assertEquals('attachment; filename="testtitleentry1.mobi"', $headers->get('content-disposition')); + $this->assertEquals('binary', $headers->get('content-transfer-encoding')); + } + + public function testPdfExport() + { + $this->logInAs('admin'); + $client = $this->getClient(); + + ob_start(); + $crawler = $client->request('GET', '/export/all.pdf'); + ob_end_clean(); + + $this->assertEquals(200, $client->getResponse()->getStatusCode()); + + $headers = $client->getResponse()->headers; + $this->assertEquals('application/pdf', $headers->get('content-type')); + $this->assertEquals('attachment; filename="All articles.pdf"', $headers->get('content-disposition')); + $this->assertEquals('binary', $headers->get('content-transfer-encoding')); + } + + public function testCsvExport() + { + $this->logInAs('admin'); + $client = $this->getClient(); + + ob_start(); + $crawler = $client->request('GET', '/export/unread.csv'); + ob_end_clean(); + + $this->assertEquals(200, $client->getResponse()->getStatusCode()); + + $headers = $client->getResponse()->headers; + $this->assertEquals('application/csv', $headers->get('content-type')); + $this->assertEquals('attachment; filename="Unread articles.csv"', $headers->get('content-disposition')); + $this->assertEquals('UTF-8', $headers->get('content-transfer-encoding')); + + $csv = str_getcsv($client->getResponse()->getContent(), "\n"); + + $this->assertGreaterThan(1, $csv); + $this->assertEquals('Title;URL;Content;Tags;"MIME Type";Language', $csv[0]); + } +} From 33c36f6b482dd512a43b86c0e965bc09a67cf555 Mon Sep 17 00:00:00 2001 From: Jeremy Benoist Date: Fri, 16 Oct 2015 10:56:24 +0200 Subject: [PATCH 03/10] Fix tests on pgsql --- .../CoreBundle/Tests/Controller/ExportControllerTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Wallabag/CoreBundle/Tests/Controller/ExportControllerTest.php b/src/Wallabag/CoreBundle/Tests/Controller/ExportControllerTest.php index 19d5f01d9..3f749aaee 100644 --- a/src/Wallabag/CoreBundle/Tests/Controller/ExportControllerTest.php +++ b/src/Wallabag/CoreBundle/Tests/Controller/ExportControllerTest.php @@ -71,7 +71,7 @@ class ExportControllerTest extends WallabagCoreTestCase $headers = $client->getResponse()->headers; $this->assertEquals('application/x-mobipocket-ebook', $headers->get('content-type')); - $this->assertEquals('attachment; filename="testtitleentry1.mobi"', $headers->get('content-disposition')); + $this->assertEquals('attachment; filename="'.preg_replace('/[^A-Za-z0-9\-]/', '', $content->getTitle()).'.mobi"', $headers->get('content-disposition')); $this->assertEquals('binary', $headers->get('content-transfer-encoding')); } From b3cc1a14e7b9939fdaf7e71fac40ed7c42727854 Mon Sep 17 00:00:00 2001 From: Thomas Citharel Date: Sun, 18 Oct 2015 15:49:00 +0200 Subject: [PATCH 04/10] add json & xml --- .../CoreBundle/Helper/EntriesExport.php | 55 +++++++++++++++++++ .../themes/material/Entry/entries.html.twig | 14 ++--- .../themes/material/Entry/entry.html.twig | 10 ++-- .../Tests/Controller/ExportControllerTest.php | 34 ++++++++++++ 4 files changed, 102 insertions(+), 11 deletions(-) diff --git a/src/Wallabag/CoreBundle/Helper/EntriesExport.php b/src/Wallabag/CoreBundle/Helper/EntriesExport.php index 806319b1a..33ff6311b 100644 --- a/src/Wallabag/CoreBundle/Helper/EntriesExport.php +++ b/src/Wallabag/CoreBundle/Helper/EntriesExport.php @@ -5,6 +5,10 @@ namespace Wallabag\CoreBundle\Helper; use PHPePub\Core\EPub; use PHPePub\Core\Structure\OPF\DublinCore; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Serializer\Serializer; +use Symfony\Component\Serializer\Encoder\XmlEncoder; +use Symfony\Component\Serializer\Encoder\JsonEncoder; +use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; class EntriesExport { @@ -86,6 +90,12 @@ class EntriesExport case 'csv': return $this->produceCSV(); + + case 'json': + return $this->produceJSON(); + + case 'xml': + return $this->produceXML(); } throw new \InvalidArgumentException(sprintf('The format "%s" is not yet supported.', $format)); @@ -319,6 +329,51 @@ class EntriesExport )->send(); } + private function produceJSON() + { + $serializer = $this->prepareSerializingContent(); + $jsonContent = $serializer->serialize($this->entries, 'json'); + + return Response::create( + $jsonContent, + 200, + array( + 'Content-type' => 'application/json', + 'Content-Disposition' => 'attachment; filename="'.$this->title.'.json"', + 'Content-Transfer-Encoding' => 'UTF-8', + ) + )->send(); + } + + private function produceXML() + { + $serializer = $this->prepareSerializingContent(); + $xmlContent = $serializer->serialize($this->entries, 'xml'); + + return Response::create( + $xmlContent, + 200, + array( + 'Content-type' => 'application/xml', + 'Content-Disposition' => 'attachment; filename="'.$this->title.'.xml"', + 'Content-Transfer-Encoding' => 'UTF-8', + ) + )->send(); + } + /** + * Return a Serializer object for producing processes that need it (JSON & XML). + * + * @return Serializer + */ + private function prepareSerializingContent() + { + $encoders = array(new XmlEncoder(), new JsonEncoder()); + $normalizers = array(new ObjectNormalizer()); + $normalizers[0]->setIgnoredAttributes(array('user', 'createdAt', 'updatedAt')); + + return new Serializer($normalizers, $encoders); + } + /** * Return a kind of footer / information for the epub. * 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 5a231c865..bf38bff82 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 @@ -99,13 +99,13 @@ {% 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 bece099a5..fd84d984e 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 @@ -106,10 +106,12 @@
      - {% if export_epub %}
    • EPUB
    • {% endif %} - {% if export_mobi %}
    • MOBI
    • {% endif %} - {% if export_pdf %}
    • PDF
    • {% endif %} -
    • CSV
    • + {% if export_epub %}
    • EPUB
    • {% endif %} + {% if export_mobi %}
    • MOBI
    • {% endif %} + {% if export_pdf %}
    • PDF
    • {% endif %} +
    • CSV
    • +
    • JSON
    • +
    • XML
    diff --git a/src/Wallabag/CoreBundle/Tests/Controller/ExportControllerTest.php b/src/Wallabag/CoreBundle/Tests/Controller/ExportControllerTest.php index 3f749aaee..febdf4d4c 100644 --- a/src/Wallabag/CoreBundle/Tests/Controller/ExportControllerTest.php +++ b/src/Wallabag/CoreBundle/Tests/Controller/ExportControllerTest.php @@ -113,4 +113,38 @@ class ExportControllerTest extends WallabagCoreTestCase $this->assertGreaterThan(1, $csv); $this->assertEquals('Title;URL;Content;Tags;"MIME Type";Language', $csv[0]); } + + public function testJsonExport() + { + $this->logInAs('admin'); + $client = $this->getClient(); + + ob_start(); + $crawler = $client->request('GET', '/export/all.json'); + ob_end_clean(); + + $this->assertEquals(200, $client->getResponse()->getStatusCode()); + + $headers = $client->getResponse()->headers; + $this->assertEquals('application/json', $headers->get('content-type')); + $this->assertEquals('attachment; filename="All articles.json"', $headers->get('content-disposition')); + $this->assertEquals('UTF-8', $headers->get('content-transfer-encoding')); + } + + public function testXmlExport() + { + $this->logInAs('admin'); + $client = $this->getClient(); + + ob_start(); + $crawler = $client->request('GET', '/export/unread.xml'); + ob_end_clean(); + + $this->assertEquals(200, $client->getResponse()->getStatusCode()); + + $headers = $client->getResponse()->headers; + $this->assertEquals('application/xml', $headers->get('content-type')); + $this->assertEquals('attachment; filename="Unread articles.xml"', $headers->get('content-disposition')); + $this->assertEquals('UTF-8', $headers->get('content-transfer-encoding')); + } } From 8ac95cbfcc2fa3e115bef369be216f7aa30f7311 Mon Sep 17 00:00:00 2001 From: Thomas Citharel Date: Sun, 18 Oct 2015 15:59:15 +0200 Subject: [PATCH 05/10] improved function --- src/Wallabag/CoreBundle/Helper/EntriesExport.php | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/Wallabag/CoreBundle/Helper/EntriesExport.php b/src/Wallabag/CoreBundle/Helper/EntriesExport.php index 33ff6311b..e073606cf 100644 --- a/src/Wallabag/CoreBundle/Helper/EntriesExport.php +++ b/src/Wallabag/CoreBundle/Helper/EntriesExport.php @@ -331,11 +331,8 @@ class EntriesExport private function produceJSON() { - $serializer = $this->prepareSerializingContent(); - $jsonContent = $serializer->serialize($this->entries, 'json'); - return Response::create( - $jsonContent, + $this->prepareSerializingContent('json'), 200, array( 'Content-type' => 'application/json', @@ -347,11 +344,8 @@ class EntriesExport private function produceXML() { - $serializer = $this->prepareSerializingContent(); - $xmlContent = $serializer->serialize($this->entries, 'xml'); - return Response::create( - $xmlContent, + $this->prepareSerializingContent('xml'), 200, array( 'Content-type' => 'application/xml', @@ -360,18 +354,20 @@ class EntriesExport ) )->send(); } + /** * Return a Serializer object for producing processes that need it (JSON & XML). * * @return Serializer */ - private function prepareSerializingContent() + private function prepareSerializingContent($format) { $encoders = array(new XmlEncoder(), new JsonEncoder()); $normalizers = array(new ObjectNormalizer()); $normalizers[0]->setIgnoredAttributes(array('user', 'createdAt', 'updatedAt')); + $serializer = new Serializer($normalizers, $encoders); - return new Serializer($normalizers, $encoders); + return $serializer->serialize($this->entries, $format); } /** From 5b7da07620116a91d3c36ccd728d1899bc3ccb46 Mon Sep 17 00:00:00 2001 From: Thomas Citharel Date: Mon, 19 Oct 2015 20:31:30 +0200 Subject: [PATCH 06/10] use the groups annotation instead of setIgnoredAttributes --- src/Wallabag/CoreBundle/Entity/Entry.php | 35 +++++++++++++++++++ .../CoreBundle/Helper/EntriesExport.php | 11 +++--- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/src/Wallabag/CoreBundle/Entity/Entry.php b/src/Wallabag/CoreBundle/Entity/Entry.php index 9e5446a64..a2bb507ea 100644 --- a/src/Wallabag/CoreBundle/Entity/Entry.php +++ b/src/Wallabag/CoreBundle/Entity/Entry.php @@ -6,6 +6,7 @@ use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Validator\Constraints as Assert; use Hateoas\Configuration\Annotation as Hateoas; +use Symfony\Component\Serializer\Annotation\Groups; use JMS\Serializer\Annotation\XmlRoot; use Wallabag\UserBundle\Entity\User; @@ -27,6 +28,8 @@ class Entry * @ORM\Column(name="id", type="integer") * @ORM\Id * @ORM\GeneratedValue(strategy="AUTO") + * + * @Groups({"entries_for_user", "export_all"}) */ private $id; @@ -34,6 +37,8 @@ class Entry * @var string * * @ORM\Column(name="title", type="text", nullable=true) + * + * @Groups({"entries_for_user", "export_all"}) */ private $title; @@ -42,6 +47,8 @@ class Entry * * @Assert\NotBlank() * @ORM\Column(name="url", type="text", nullable=true) + * + * @Groups({"entries_for_user", "export_all"}) */ private $url; @@ -49,6 +56,8 @@ class Entry * @var bool * * @ORM\Column(name="is_archived", type="boolean") + * + * @Groups({"entries_for_user", "export_all"}) */ private $isArchived = false; @@ -56,6 +65,8 @@ class Entry * @var bool * * @ORM\Column(name="is_starred", type="boolean") + * + * @Groups({"entries_for_user", "export_all"}) */ private $isStarred = false; @@ -63,6 +74,8 @@ class Entry * @var string * * @ORM\Column(name="content", type="text", nullable=true) + * + * @Groups({"entries_for_user", "export_all"}) */ private $content; @@ -70,6 +83,8 @@ class Entry * @var date * * @ORM\Column(name="created_at", type="datetime") + * + * @Groups({"export_all"}) */ private $createdAt; @@ -77,6 +92,8 @@ class Entry * @var date * * @ORM\Column(name="updated_at", type="datetime") + * + * @Groups({"export_all"}) */ private $updatedAt; @@ -84,6 +101,8 @@ class Entry * @var string * * @ORM\Column(name="comments", type="text", nullable=true) + * + * @Groups({"export_all"}) */ private $comments; @@ -91,6 +110,8 @@ class Entry * @var string * * @ORM\Column(name="mimetype", type="text", nullable=true) + * + * @Groups({"entries_for_user", "export_all"}) */ private $mimetype; @@ -98,6 +119,8 @@ class Entry * @var string * * @ORM\Column(name="language", type="text", nullable=true) + * + * @Groups({"entries_for_user", "export_all"}) */ private $language; @@ -105,6 +128,8 @@ class Entry * @var int * * @ORM\Column(name="reading_time", type="integer", nullable=true) + * + * @Groups({"entries_for_user", "export_all"}) */ private $readingTime; @@ -112,6 +137,8 @@ class Entry * @var string * * @ORM\Column(name="domain_name", type="text", nullable=true) + * + * @Groups({"entries_for_user", "export_all"}) */ private $domainName; @@ -119,6 +146,8 @@ class Entry * @var string * * @ORM\Column(name="preview_picture", type="text", nullable=true) + * + * @Groups({"entries_for_user", "export_all"}) */ private $previewPicture; @@ -126,17 +155,23 @@ class Entry * @var bool * * @ORM\Column(name="is_public", type="boolean", nullable=true, options={"default" = false}) + * + * @Groups({"export_all"}) */ private $isPublic; /** * @ORM\ManyToOne(targetEntity="Wallabag\UserBundle\Entity\User", inversedBy="entries") + * + * @Groups({"export_all"}) */ private $user; /** * @ORM\ManyToMany(targetEntity="Tag", inversedBy="entries", cascade={"persist"}) * @ORM\JoinTable + * + * @Groups({"entries_for_user", "export_all"}) */ private $tags; diff --git a/src/Wallabag/CoreBundle/Helper/EntriesExport.php b/src/Wallabag/CoreBundle/Helper/EntriesExport.php index e073606cf..f073ed7f4 100644 --- a/src/Wallabag/CoreBundle/Helper/EntriesExport.php +++ b/src/Wallabag/CoreBundle/Helper/EntriesExport.php @@ -8,7 +8,10 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Serializer\Serializer; use Symfony\Component\Serializer\Encoder\XmlEncoder; use Symfony\Component\Serializer\Encoder\JsonEncoder; -use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; +use Symfony\Component\Serializer\Normalizer\PropertyNormalizer; +use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; +use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; +use Doctrine\Common\Annotations\AnnotationReader; class EntriesExport { @@ -363,11 +366,11 @@ class EntriesExport private function prepareSerializingContent($format) { $encoders = array(new XmlEncoder(), new JsonEncoder()); - $normalizers = array(new ObjectNormalizer()); - $normalizers[0]->setIgnoredAttributes(array('user', 'createdAt', 'updatedAt')); + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $normalizers = array(new PropertyNormalizer($classMetadataFactory)); $serializer = new Serializer($normalizers, $encoders); - return $serializer->serialize($this->entries, $format); + return $serializer->serialize($this->entries, $format, array('groups' => array('entries_for_user'))); } /** From 268e9e7277d470dbd65b4eaa70c247ef35a95a3d Mon Sep 17 00:00:00 2001 From: Thomas Citharel Date: Mon, 19 Oct 2015 21:17:30 +0200 Subject: [PATCH 07/10] use JMS Serializer --- src/Wallabag/CoreBundle/Entity/Entry.php | 2 +- .../CoreBundle/Helper/EntriesExport.php | 17 +++++------------ 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/src/Wallabag/CoreBundle/Entity/Entry.php b/src/Wallabag/CoreBundle/Entity/Entry.php index a2bb507ea..5aa582f8d 100644 --- a/src/Wallabag/CoreBundle/Entity/Entry.php +++ b/src/Wallabag/CoreBundle/Entity/Entry.php @@ -6,7 +6,7 @@ use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Validator\Constraints as Assert; use Hateoas\Configuration\Annotation as Hateoas; -use Symfony\Component\Serializer\Annotation\Groups; +use JMS\Serializer\Annotation\Groups; use JMS\Serializer\Annotation\XmlRoot; use Wallabag\UserBundle\Entity\User; diff --git a/src/Wallabag/CoreBundle/Helper/EntriesExport.php b/src/Wallabag/CoreBundle/Helper/EntriesExport.php index f073ed7f4..c14f9d72f 100644 --- a/src/Wallabag/CoreBundle/Helper/EntriesExport.php +++ b/src/Wallabag/CoreBundle/Helper/EntriesExport.php @@ -5,13 +5,9 @@ namespace Wallabag\CoreBundle\Helper; use PHPePub\Core\EPub; use PHPePub\Core\Structure\OPF\DublinCore; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Serializer\Serializer; -use Symfony\Component\Serializer\Encoder\XmlEncoder; -use Symfony\Component\Serializer\Encoder\JsonEncoder; -use Symfony\Component\Serializer\Normalizer\PropertyNormalizer; -use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; -use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; -use Doctrine\Common\Annotations\AnnotationReader; +use JMS\Serializer; +use JMS\Serializer\SerializerBuilder; +use JMS\Serializer\SerializationContext; class EntriesExport { @@ -365,12 +361,9 @@ class EntriesExport */ private function prepareSerializingContent($format) { - $encoders = array(new XmlEncoder(), new JsonEncoder()); - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); - $normalizers = array(new PropertyNormalizer($classMetadataFactory)); - $serializer = new Serializer($normalizers, $encoders); + $serializer = SerializerBuilder::create()->build(); - return $serializer->serialize($this->entries, $format, array('groups' => array('entries_for_user'))); + return $serializer->serialize($this->entries, $format, SerializationContext::create()->setGroups(array('entries_for_user'))); } /** From cceca9ea1d93ccf1420c2506330a16dc07f6433c Mon Sep 17 00:00:00 2001 From: Jeremy Benoist Date: Fri, 30 Oct 2015 20:57:10 +0100 Subject: [PATCH 08/10] Fix route parameters Improve export tests Improve CSV export --- .../Controller/ExportController.php | 11 ++- .../CoreBundle/Helper/EntriesExport.php | 12 ++- .../Tests/Controller/ExportControllerTest.php | 93 ++++++++++++++++++- 3 files changed, 108 insertions(+), 8 deletions(-) diff --git a/src/Wallabag/CoreBundle/Controller/ExportController.php b/src/Wallabag/CoreBundle/Controller/ExportController.php index dd3cb7ca5..c8ef49a2f 100644 --- a/src/Wallabag/CoreBundle/Controller/ExportController.php +++ b/src/Wallabag/CoreBundle/Controller/ExportController.php @@ -7,6 +7,10 @@ use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Wallabag\CoreBundle\Entity\Entry; +/** + * The try/catch can be removed once all formats will be implemented. + * Still need implementation: txt. + */ class ExportController extends Controller { /** @@ -14,7 +18,10 @@ class ExportController extends Controller * * @param Entry $entry * - * @Route("/export/{id}.{format}", requirements={"id" = "\d+"}, name="export_entry") + * @Route("/export/{id}.{format}", name="export_entry", requirements={ + * "format": "epub|mobi|pdf|json|xml|txt|csv", + * "id": "\d+" + * }) */ public function downloadEntryAction(Entry $entry, $format) { @@ -32,7 +39,7 @@ class ExportController extends Controller * Export all entries for current user. * * @Route("/export/{category}.{format}", name="export_entries", requirements={ - * "_format": "epub|mobi|pdf|json|xml|txt|csv", + * "format": "epub|mobi|pdf|json|xml|txt|csv", * "category": "all|unread|starred|archive" * }) */ diff --git a/src/Wallabag/CoreBundle/Helper/EntriesExport.php b/src/Wallabag/CoreBundle/Helper/EntriesExport.php index c14f9d72f..d6a4d094a 100644 --- a/src/Wallabag/CoreBundle/Helper/EntriesExport.php +++ b/src/Wallabag/CoreBundle/Helper/EntriesExport.php @@ -9,6 +9,9 @@ use JMS\Serializer; use JMS\Serializer\SerializerBuilder; use JMS\Serializer\SerializationContext; +/** + * This class doesn't have unit test BUT it's fully covered by a functional test with ExportControllerTest. + */ class EntriesExport { private $wallabagUrl; @@ -303,7 +306,8 @@ class EntriesExport array( $entry->getTitle(), $entry->getURL(), - $entry->getContent(), + // remove new line to avoid crazy results + str_replace(array("\r\n", "\r", "\n"), '', $entry->getContent()), implode(', ', $entry->getTags()->toArray()), $entry->getMimetype(), $entry->getLanguage(), @@ -363,7 +367,11 @@ class EntriesExport { $serializer = SerializerBuilder::create()->build(); - return $serializer->serialize($this->entries, $format, SerializationContext::create()->setGroups(array('entries_for_user'))); + return $serializer->serialize( + $this->entries, + $format, + SerializationContext::create()->setGroups(array('entries_for_user')) + ); } /** diff --git a/src/Wallabag/CoreBundle/Tests/Controller/ExportControllerTest.php b/src/Wallabag/CoreBundle/Tests/Controller/ExportControllerTest.php index febdf4d4c..3d3b97a99 100644 --- a/src/Wallabag/CoreBundle/Tests/Controller/ExportControllerTest.php +++ b/src/Wallabag/CoreBundle/Tests/Controller/ExportControllerTest.php @@ -21,7 +21,7 @@ class ExportControllerTest extends WallabagCoreTestCase $this->logInAs('admin'); $client = $this->getClient(); - $crawler = $client->request('GET', '/export/awesomeness.epub'); + $client->request('GET', '/export/awesomeness.epub'); $this->assertEquals(404, $client->getResponse()->getStatusCode()); } @@ -31,7 +31,34 @@ class ExportControllerTest extends WallabagCoreTestCase $this->logInAs('admin'); $client = $this->getClient(); - $crawler = $client->request('GET', '/export/unread.xslx'); + $client->request('GET', '/export/unread.xslx'); + + $this->assertEquals(404, $client->getResponse()->getStatusCode()); + } + + public function testUnsupportedFormatExport() + { + $this->logInAs('admin'); + $client = $this->getClient(); + + $client->request('GET', '/export/unread.txt'); + $this->assertEquals(404, $client->getResponse()->getStatusCode()); + + $content = $client->getContainer() + ->get('doctrine.orm.entity_manager') + ->getRepository('WallabagCoreBundle:Entry') + ->findOneByUsernameAndNotArchived('admin'); + + $client->request('GET', '/export/'.$content->getId().'.txt'); + $this->assertEquals(404, $client->getResponse()->getStatusCode()); + } + + public function testBadEntryId() + { + $this->logInAs('admin'); + $client = $this->getClient(); + + $client->request('GET', '/export/0.mobi'); $this->assertEquals(404, $client->getResponse()->getStatusCode()); } @@ -97,20 +124,33 @@ class ExportControllerTest extends WallabagCoreTestCase $this->logInAs('admin'); $client = $this->getClient(); + // to be sure results are the same + $contentInDB = $client->getContainer() + ->get('doctrine.orm.entity_manager') + ->getRepository('WallabagCoreBundle:Entry') + ->createQueryBuilder('e') + ->leftJoin('e.user', 'u') + ->where('u.username = :username')->setParameter('username', 'admin') + ->andWhere('e.isArchived = true') + ->getQuery() + ->getArrayResult(); + ob_start(); - $crawler = $client->request('GET', '/export/unread.csv'); + $crawler = $client->request('GET', '/export/archive.csv'); ob_end_clean(); $this->assertEquals(200, $client->getResponse()->getStatusCode()); $headers = $client->getResponse()->headers; $this->assertEquals('application/csv', $headers->get('content-type')); - $this->assertEquals('attachment; filename="Unread articles.csv"', $headers->get('content-disposition')); + $this->assertEquals('attachment; filename="Archive articles.csv"', $headers->get('content-disposition')); $this->assertEquals('UTF-8', $headers->get('content-transfer-encoding')); $csv = str_getcsv($client->getResponse()->getContent(), "\n"); $this->assertGreaterThan(1, $csv); + // +1 for title line + $this->assertEquals(count($contentInDB)+1, count($csv)); $this->assertEquals('Title;URL;Content;Tags;"MIME Type";Language', $csv[0]); } @@ -119,6 +159,16 @@ class ExportControllerTest extends WallabagCoreTestCase $this->logInAs('admin'); $client = $this->getClient(); + // to be sure results are the same + $contentInDB = $client->getContainer() + ->get('doctrine.orm.entity_manager') + ->getRepository('WallabagCoreBundle:Entry') + ->createQueryBuilder('e') + ->leftJoin('e.user', 'u') + ->where('u.username = :username')->setParameter('username', 'admin') + ->getQuery() + ->getArrayResult(); + ob_start(); $crawler = $client->request('GET', '/export/all.json'); ob_end_clean(); @@ -129,6 +179,21 @@ class ExportControllerTest extends WallabagCoreTestCase $this->assertEquals('application/json', $headers->get('content-type')); $this->assertEquals('attachment; filename="All articles.json"', $headers->get('content-disposition')); $this->assertEquals('UTF-8', $headers->get('content-transfer-encoding')); + + $content = json_decode($client->getResponse()->getContent(), true); + $this->assertEquals(count($contentInDB), count($content)); + $this->assertArrayHasKey('id', $content[0]); + $this->assertArrayHasKey('title', $content[0]); + $this->assertArrayHasKey('url', $content[0]); + $this->assertArrayHasKey('is_archived', $content[0]); + $this->assertArrayHasKey('is_starred', $content[0]); + $this->assertArrayHasKey('content', $content[0]); + $this->assertArrayHasKey('mimetype', $content[0]); + $this->assertArrayHasKey('language', $content[0]); + $this->assertArrayHasKey('reading_time', $content[0]); + $this->assertArrayHasKey('domain_name', $content[0]); + $this->assertArrayHasKey('preview_picture', $content[0]); + $this->assertArrayHasKey('tags', $content[0]); } public function testXmlExport() @@ -136,6 +201,17 @@ class ExportControllerTest extends WallabagCoreTestCase $this->logInAs('admin'); $client = $this->getClient(); + // to be sure results are the same + $contentInDB = $client->getContainer() + ->get('doctrine.orm.entity_manager') + ->getRepository('WallabagCoreBundle:Entry') + ->createQueryBuilder('e') + ->leftJoin('e.user', 'u') + ->where('u.username = :username')->setParameter('username', 'admin') + ->andWhere('e.isArchived = false') + ->getQuery() + ->getArrayResult(); + ob_start(); $crawler = $client->request('GET', '/export/unread.xml'); ob_end_clean(); @@ -146,5 +222,14 @@ class ExportControllerTest extends WallabagCoreTestCase $this->assertEquals('application/xml', $headers->get('content-type')); $this->assertEquals('attachment; filename="Unread articles.xml"', $headers->get('content-disposition')); $this->assertEquals('UTF-8', $headers->get('content-transfer-encoding')); + + $content = new \SimpleXMLElement($client->getResponse()->getContent()); + $this->assertGreaterThan(0, $content->count()); + $this->assertEquals(count($contentInDB), $content->count()); + $this->assertNotEmpty('id', (string) $content->entry[0]->id); + $this->assertNotEmpty('title', (string) $content->entry[0]->title); + $this->assertNotEmpty('url', (string) $content->entry[0]->url); + $this->assertNotEmpty('content', (string) $content->entry[0]->content); + $this->assertNotEmpty('domain_name', (string) $content->entry[0]->domain_name); } } From fba3f536a5557b9094393728d360bf78260ab4da Mon Sep 17 00:00:00 2001 From: Jeremy Benoist Date: Sat, 31 Oct 2015 09:26:00 +0100 Subject: [PATCH 09/10] Fix tests --- src/Wallabag/CoreBundle/DataFixtures/ORM/LoadEntryData.php | 6 ++++++ .../CoreBundle/Tests/Controller/ExportControllerTest.php | 1 - 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadEntryData.php b/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadEntryData.php index 7e64c5e1c..176c529e1 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 $entry1->setUrl('http://0.0.0.0'); $entry1->setReadingTime(11); $entry1->setDomainName('domain.io'); + $entry1->setMimetype('text/html'); $entry1->setTitle('test title entry1'); $entry1->setContent('This is my content /o/'); $entry1->setLanguage('en'); @@ -31,6 +32,7 @@ class LoadEntryData extends AbstractFixture implements OrderedFixtureInterface $entry2->setUrl('http://0.0.0.0'); $entry2->setReadingTime(1); $entry2->setDomainName('domain.io'); + $entry2->setMimetype('text/html'); $entry2->setTitle('test title entry2'); $entry2->setContent('This is my content /o/'); $entry2->setLanguage('fr'); @@ -43,6 +45,7 @@ class LoadEntryData extends AbstractFixture implements OrderedFixtureInterface $entry3->setUrl('http://0.0.0.0'); $entry3->setReadingTime(1); $entry3->setDomainName('domain.io'); + $entry3->setMimetype('text/html'); $entry3->setTitle('test title entry3'); $entry3->setContent('This is my content /o/'); $entry3->setLanguage('en'); @@ -63,6 +66,7 @@ class LoadEntryData extends AbstractFixture implements OrderedFixtureInterface $entry4->setUrl('http://0.0.0.0'); $entry4->setReadingTime(12); $entry4->setDomainName('domain.io'); + $entry4->setMimetype('text/html'); $entry4->setTitle('test title entry4'); $entry4->setContent('This is my content /o/'); $entry4->setLanguage('en'); @@ -83,6 +87,7 @@ class LoadEntryData extends AbstractFixture implements OrderedFixtureInterface $entry5->setUrl('http://0.0.0.0'); $entry5->setReadingTime(12); $entry5->setDomainName('domain.io'); + $entry5->setMimetype('text/html'); $entry5->setTitle('test title entry5'); $entry5->setContent('This is my content /o/'); $entry5->setStarred(true); @@ -97,6 +102,7 @@ class LoadEntryData extends AbstractFixture implements OrderedFixtureInterface $entry6->setUrl('http://0.0.0.0'); $entry6->setReadingTime(12); $entry6->setDomainName('domain.io'); + $entry6->setMimetype('text/html'); $entry6->setTitle('test title entry6'); $entry6->setContent('This is my content /o/'); $entry6->setArchived(true); diff --git a/src/Wallabag/CoreBundle/Tests/Controller/ExportControllerTest.php b/src/Wallabag/CoreBundle/Tests/Controller/ExportControllerTest.php index 3d3b97a99..739b2dec7 100644 --- a/src/Wallabag/CoreBundle/Tests/Controller/ExportControllerTest.php +++ b/src/Wallabag/CoreBundle/Tests/Controller/ExportControllerTest.php @@ -192,7 +192,6 @@ class ExportControllerTest extends WallabagCoreTestCase $this->assertArrayHasKey('language', $content[0]); $this->assertArrayHasKey('reading_time', $content[0]); $this->assertArrayHasKey('domain_name', $content[0]); - $this->assertArrayHasKey('preview_picture', $content[0]); $this->assertArrayHasKey('tags', $content[0]); } From 16bbb4aa417188e7c21eb4a1734adf0f0c9b25f9 Mon Sep 17 00:00:00 2001 From: Jeremy Benoist Date: Mon, 2 Nov 2015 07:58:31 +0100 Subject: [PATCH 10/10] Update deps --- composer.lock | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/composer.lock b/composer.lock index ae53a6d61..b7b5d142b 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "6bd09434f83c7e6b5e1c75fddbd7608b", - "content-hash": "d07d54c4cc6f4f4947c652bd659af02e", + "hash": "a9ec461e17166dcda1563dd55f6ff861", "packages": [ { "name": "doctrine/annotations", @@ -1632,20 +1631,20 @@ } ], "description": "Graby helps you extract article content from web pages", - "time": "2015-10-14 17:55:10" + "time": "2015-10-28 06:38:06" }, { "name": "j0k3r/graby-site-config", - "version": "1.0.5", + "version": "1.0.6", "source": { "type": "git", "url": "https://github.com/j0k3r/graby-site-config.git", - "reference": "44afb3d6a90a816752426537e92e85160bdb40b9" + "reference": "6631451c1be563ff3bc8fa16c60ef7df38f8e65b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/j0k3r/graby-site-config/zipball/44afb3d6a90a816752426537e92e85160bdb40b9", - "reference": "44afb3d6a90a816752426537e92e85160bdb40b9", + "url": "https://api.github.com/repos/j0k3r/graby-site-config/zipball/6631451c1be563ff3bc8fa16c60ef7df38f8e65b", + "reference": "6631451c1be563ff3bc8fa16c60ef7df38f8e65b", "shasum": "" }, "require": { @@ -2138,16 +2137,16 @@ }, { "name": "lexik/form-filter-bundle", - "version": "v4.0.1", + "version": "v4.0.2", "source": { "type": "git", "url": "https://github.com/lexik/LexikFormFilterBundle.git", - "reference": "8e54a41376eab7890d4deea4088ad5c86df964ee" + "reference": "d6081d308b71e14509602722c78d28801e4ed78a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/lexik/LexikFormFilterBundle/zipball/8e54a41376eab7890d4deea4088ad5c86df964ee", - "reference": "8e54a41376eab7890d4deea4088ad5c86df964ee", + "url": "https://api.github.com/repos/lexik/LexikFormFilterBundle/zipball/d6081d308b71e14509602722c78d28801e4ed78a", + "reference": "d6081d308b71e14509602722c78d28801e4ed78a", "shasum": "" }, "require": { @@ -3800,12 +3799,12 @@ "version": "1.0.1", "source": { "type": "git", - "url": "https://github.com/wallabag/phpMobi.git", + "url": "https://github.com/wallabag/php-mobi.git", "reference": "1cd7d022fe6be838535d6bba917d19cc48dcf487" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/wallabag/phpMobi/zipball/1cd7d022fe6be838535d6bba917d19cc48dcf487", + "url": "https://api.github.com/repos/wallabag/php-mobi/zipball/1cd7d022fe6be838535d6bba917d19cc48dcf487", "reference": "1cd7d022fe6be838535d6bba917d19cc48dcf487", "shasum": "" }, @@ -3839,7 +3838,8 @@ "description": "A Mobipocket file (.mobi) creator in PHP.", "homepage": "https://github.com/wallabag/phpMobi", "support": { - "source": "https://github.com/wallabag/phpMobi/tree/1.0.1" + "source": "https://github.com/wallabag/php-mobi/tree/1.0.1", + "issues": "https://github.com/wallabag/php-mobi/issues" }, "time": "2015-10-16 08:42:42" }, @@ -3949,7 +3949,7 @@ ], "authors": [ { - "name": "William Durand", + "name": "William DURAND", "email": "william.durand1@gmail.com" } ],