diff options
author | Nicolas Lœuillet <nicolas@loeuillet.org> | 2015-11-09 16:45:48 +0100 |
---|---|---|
committer | Nicolas Lœuillet <nicolas@loeuillet.org> | 2015-11-09 16:45:48 +0100 |
commit | 0a0c600887dde4cc755de0862a3301830c415882 (patch) | |
tree | 4d82bc16e921248bb6ab1b203a33e1b55b4ff445 | |
parent | f1eccfd63f214dcc730ab0d18a694a5465f425db (diff) | |
parent | 16bbb4aa417188e7c21eb4a1734adf0f0c9b25f9 (diff) | |
download | wallabag-0a0c600887dde4cc755de0862a3301830c415882.tar.gz wallabag-0a0c600887dde4cc755de0862a3301830c415882.tar.zst wallabag-0a0c600887dde4cc755de0862a3301830c415882.zip |
Merge pull request #1422 from wallabag/v2-ebook
V2 – Export entries
16 files changed, 1138 insertions, 8 deletions
diff --git a/app/config/parameters.yml.dist b/app/config/parameters.yml.dist index 52f9bccb..b475d637 100644 --- a/app/config/parameters.yml.dist +++ b/app/config/parameters.yml.dist | |||
@@ -51,6 +51,7 @@ parameters: | |||
51 | export_epub: true | 51 | export_epub: true |
52 | export_mobi: true | 52 | export_mobi: true |
53 | export_pdf: true | 53 | export_pdf: true |
54 | wallabag_url: http://v2.wallabag.org | ||
54 | 55 | ||
55 | # default user config | 56 | # default user config |
56 | items_on_page: 12 | 57 | items_on_page: 12 |
diff --git a/app/config/tests/parameters.yml.dist.mysql b/app/config/tests/parameters.yml.dist.mysql index 03fdf5a6..5b29690c 100644 --- a/app/config/tests/parameters.yml.dist.mysql +++ b/app/config/tests/parameters.yml.dist.mysql | |||
@@ -51,6 +51,7 @@ parameters: | |||
51 | export_epub: true | 51 | export_epub: true |
52 | export_mobi: true | 52 | export_mobi: true |
53 | export_pdf: true | 53 | export_pdf: true |
54 | wallabag_url: http://v2.wallabag.org | ||
54 | 55 | ||
55 | # default user config | 56 | # default user config |
56 | items_on_page: 12 | 57 | items_on_page: 12 |
diff --git a/app/config/tests/parameters.yml.dist.pgsql b/app/config/tests/parameters.yml.dist.pgsql index 675ba6c9..efdac961 100644 --- a/app/config/tests/parameters.yml.dist.pgsql +++ b/app/config/tests/parameters.yml.dist.pgsql | |||
@@ -51,6 +51,7 @@ parameters: | |||
51 | export_epub: true | 51 | export_epub: true |
52 | export_mobi: true | 52 | export_mobi: true |
53 | export_pdf: true | 53 | export_pdf: true |
54 | wallabag_url: http://v2.wallabag.org | ||
54 | 55 | ||
55 | # default user config | 56 | # default user config |
56 | items_on_page: 12 | 57 | items_on_page: 12 |
diff --git a/app/config/tests/parameters.yml.dist.sqlite b/app/config/tests/parameters.yml.dist.sqlite index 258627af..276d1147 100644 --- a/app/config/tests/parameters.yml.dist.sqlite +++ b/app/config/tests/parameters.yml.dist.sqlite | |||
@@ -51,6 +51,7 @@ parameters: | |||
51 | export_epub: true | 51 | export_epub: true |
52 | export_mobi: true | 52 | export_mobi: true |
53 | export_pdf: true | 53 | export_pdf: true |
54 | wallabag_url: http://v2.wallabag.org | ||
54 | 55 | ||
55 | # default user config | 56 | # default user config |
56 | items_on_page: 12 | 57 | items_on_page: 12 |
diff --git a/composer.json b/composer.json index a46e990a..b6a9c854 100644 --- a/composer.json +++ b/composer.json | |||
@@ -55,7 +55,9 @@ | |||
55 | "j0k3r/graby": "~1.0", | 55 | "j0k3r/graby": "~1.0", |
56 | "friendsofsymfony/user-bundle": "dev-master", | 56 | "friendsofsymfony/user-bundle": "dev-master", |
57 | "friendsofsymfony/oauth-server-bundle": "^1.4@dev", | 57 | "friendsofsymfony/oauth-server-bundle": "^1.4@dev", |
58 | "scheb/two-factor-bundle": "~1.4" | 58 | "scheb/two-factor-bundle": "~1.4", |
59 | "grandt/phpepub": "~4.0", | ||
60 | "wallabag/php-mobi": "~1.0.0" | ||
59 | }, | 61 | }, |
60 | "require-dev": { | 62 | "require-dev": { |
61 | "doctrine/doctrine-fixtures-bundle": "~2.2.0", | 63 | "doctrine/doctrine-fixtures-bundle": "~2.2.0", |
@@ -63,6 +65,12 @@ | |||
63 | "phpunit/phpunit": "~4.4", | 65 | "phpunit/phpunit": "~4.4", |
64 | "symfony/phpunit-bridge": "~2.7.0" | 66 | "symfony/phpunit-bridge": "~2.7.0" |
65 | }, | 67 | }, |
68 | "repositories": [ | ||
69 | { | ||
70 | "type": "vcs", | ||
71 | "url": "https://github.com/wallabag/phpMobi" | ||
72 | } | ||
73 | ], | ||
66 | "scripts": { | 74 | "scripts": { |
67 | "post-install-cmd": [ | 75 | "post-install-cmd": [ |
68 | "Incenteev\\ParameterHandler\\ScriptHandler::buildParameters", | 76 | "Incenteev\\ParameterHandler\\ScriptHandler::buildParameters", |
diff --git a/composer.lock b/composer.lock index ec11324f..b7b5d142 100644 --- a/composer.lock +++ b/composer.lock | |||
@@ -4,8 +4,7 @@ | |||
4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", | 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", |
5 | "This file is @generated automatically" | 5 | "This file is @generated automatically" |
6 | ], | 6 | ], |
7 | "hash": "6bd09434f83c7e6b5e1c75fddbd7608b", | 7 | "hash": "a9ec461e17166dcda1563dd55f6ff861", |
8 | "content-hash": "d07d54c4cc6f4f4947c652bd659af02e", | ||
9 | "packages": [ | 8 | "packages": [ |
10 | { | 9 | { |
11 | "name": "doctrine/annotations", | 10 | "name": "doctrine/annotations", |
@@ -1089,6 +1088,244 @@ | |||
1089 | "time": "2015-11-03 10:24:23" | 1088 | "time": "2015-11-03 10:24:23" |
1090 | }, | 1089 | }, |
1091 | { | 1090 | { |
1091 | "name": "grandt/binstring", | ||
1092 | "version": "1.0.0", | ||
1093 | "source": { | ||
1094 | "type": "git", | ||
1095 | "url": "https://github.com/Grandt/PHPBinString.git", | ||
1096 | "reference": "825fe2ac8a68190f651fc2dbc07b6edde18bc431" | ||
1097 | }, | ||
1098 | "dist": { | ||
1099 | "type": "zip", | ||
1100 | "url": "https://api.github.com/repos/Grandt/PHPBinString/zipball/825fe2ac8a68190f651fc2dbc07b6edde18bc431", | ||
1101 | "reference": "825fe2ac8a68190f651fc2dbc07b6edde18bc431", | ||
1102 | "shasum": "" | ||
1103 | }, | ||
1104 | "require": { | ||
1105 | "php": ">=5.0" | ||
1106 | }, | ||
1107 | "type": "library", | ||
1108 | "autoload": { | ||
1109 | "classmap": [ | ||
1110 | "BinString.php", | ||
1111 | "BinStringStatic.php" | ||
1112 | ] | ||
1113 | }, | ||
1114 | "notification-url": "https://packagist.org/downloads/", | ||
1115 | "license": [ | ||
1116 | "LGPL-2.1" | ||
1117 | ], | ||
1118 | "authors": [ | ||
1119 | { | ||
1120 | "name": "A. Grandt", | ||
1121 | "email": "php@grandt.com", | ||
1122 | "role": "Developer" | ||
1123 | } | ||
1124 | ], | ||
1125 | "description": "A class for working around the use of mbstring.func_override", | ||
1126 | "homepage": "https://github.com/Grandt/PHPBinString", | ||
1127 | "keywords": [ | ||
1128 | "binary strings", | ||
1129 | "mbstring" | ||
1130 | ], | ||
1131 | "time": "2015-08-13 06:14:41" | ||
1132 | }, | ||
1133 | { | ||
1134 | "name": "grandt/phpepub", | ||
1135 | "version": "4.0.3", | ||
1136 | "source": { | ||
1137 | "type": "git", | ||
1138 | "url": "https://github.com/Grandt/PHPePub.git", | ||
1139 | "reference": "dee0c5549a8d2c6bf6a1ad5b4ee21d245b711fca" | ||
1140 | }, | ||
1141 | "dist": { | ||
1142 | "type": "zip", | ||
1143 | "url": "https://api.github.com/repos/Grandt/PHPePub/zipball/dee0c5549a8d2c6bf6a1ad5b4ee21d245b711fca", | ||
1144 | "reference": "dee0c5549a8d2c6bf6a1ad5b4ee21d245b711fca", | ||
1145 | "shasum": "" | ||
1146 | }, | ||
1147 | "require": { | ||
1148 | "grandt/phpresizegif": ">=1.0.3", | ||
1149 | "grandt/relativepath": ">=1.0.1", | ||
1150 | "php": ">=5.3.0", | ||
1151 | "phpzip/phpzip": ">=2.0.7" | ||
1152 | }, | ||
1153 | "type": "library", | ||
1154 | "autoload": { | ||
1155 | "psr-4": { | ||
1156 | "PHPePub\\": "src/PHPePub" | ||
1157 | }, | ||
1158 | "classmap": [ | ||
1159 | "src/lib.uuid.php" | ||
1160 | ] | ||
1161 | }, | ||
1162 | "notification-url": "https://packagist.org/downloads/", | ||
1163 | "license": [ | ||
1164 | "LGPL-2.1" | ||
1165 | ], | ||
1166 | "authors": [ | ||
1167 | { | ||
1168 | "name": "A. Grandt", | ||
1169 | "email": "php@grandt.com", | ||
1170 | "homepage": "http://grandt.com", | ||
1171 | "role": "Developer" | ||
1172 | } | ||
1173 | ], | ||
1174 | "description": "Package to create and stream e-books in the ePub 2.0 and 3.0 formats.", | ||
1175 | "homepage": "https://github.com/Grandt/PHPZip", | ||
1176 | "keywords": [ | ||
1177 | "e-book", | ||
1178 | "epub" | ||
1179 | ], | ||
1180 | "time": "2015-09-15 08:47:09" | ||
1181 | }, | ||
1182 | { | ||
1183 | "name": "grandt/phpresizegif", | ||
1184 | "version": "1.0.3", | ||
1185 | "source": { | ||
1186 | "type": "git", | ||
1187 | "url": "https://github.com/Grandt/PHPResizeGif.git", | ||
1188 | "reference": "775f6810fcda2fd1d8ca881d44a80c8d310ae7fe" | ||
1189 | }, | ||
1190 | "dist": { | ||
1191 | "type": "zip", | ||
1192 | "url": "https://api.github.com/repos/Grandt/PHPResizeGif/zipball/775f6810fcda2fd1d8ca881d44a80c8d310ae7fe", | ||
1193 | "reference": "775f6810fcda2fd1d8ca881d44a80c8d310ae7fe", | ||
1194 | "shasum": "" | ||
1195 | }, | ||
1196 | "require": { | ||
1197 | "grandt/binstring": ">=0.2.0", | ||
1198 | "php": ">=5.3.0" | ||
1199 | }, | ||
1200 | "type": "library", | ||
1201 | "autoload": { | ||
1202 | "psr-4": { | ||
1203 | "grandt\\ResizeGif\\": "src/ResizeGif", | ||
1204 | "grandt\\ResizeGif\\Files\\": "src/ResizeGif/Files", | ||
1205 | "grandt\\ResizeGif\\Structure\\": "src/ResizeGif/Structure", | ||
1206 | "grandt\\ResizeGif\\Debug\\": "src/ResizeGif/Debug" | ||
1207 | } | ||
1208 | }, | ||
1209 | "notification-url": "https://packagist.org/downloads/", | ||
1210 | "license": [ | ||
1211 | "LGPL-2.1" | ||
1212 | ], | ||
1213 | "authors": [ | ||
1214 | { | ||
1215 | "name": "A. Grandt", | ||
1216 | "email": "php@grandt.com", | ||
1217 | "homepage": "http://grandt.com", | ||
1218 | "role": "Developer" | ||
1219 | } | ||
1220 | ], | ||
1221 | "description": "GIF89a compliant Gif resizer, including transparency and optimized gifs with sub sized elements.", | ||
1222 | "homepage": "https://github.com/Grandt/PHPResizeGif", | ||
1223 | "keywords": [ | ||
1224 | "GIF89a", | ||
1225 | "animated gif", | ||
1226 | "gif", | ||
1227 | "resize" | ||
1228 | ], | ||
1229 | "time": "2015-05-10 10:52:24" | ||
1230 | }, | ||
1231 | { | ||
1232 | "name": "grandt/phpzipmerge", | ||
1233 | "version": "1.0.4", | ||
1234 | "source": { | ||
1235 | "type": "git", | ||
1236 | "url": "https://github.com/Grandt/PHPZipMerge.git", | ||
1237 | "reference": "0b1273d3c2dbfe244904158b1dbd65a663264fb9" | ||
1238 | }, | ||
1239 | "dist": { | ||
1240 | "type": "zip", | ||
1241 | "url": "https://api.github.com/repos/Grandt/PHPZipMerge/zipball/0b1273d3c2dbfe244904158b1dbd65a663264fb9", | ||
1242 | "reference": "0b1273d3c2dbfe244904158b1dbd65a663264fb9", | ||
1243 | "shasum": "" | ||
1244 | }, | ||
1245 | "require": { | ||
1246 | "grandt/binstring": ">=1.0.0", | ||
1247 | "grandt/relativepath": ">=1.0.1", | ||
1248 | "php": ">=5.3.0" | ||
1249 | }, | ||
1250 | "type": "library", | ||
1251 | "autoload": { | ||
1252 | "psr-4": { | ||
1253 | "ZipMerge\\": "src/ZipMerge" | ||
1254 | } | ||
1255 | }, | ||
1256 | "notification-url": "https://packagist.org/downloads/", | ||
1257 | "license": [ | ||
1258 | "LGPL-2.1" | ||
1259 | ], | ||
1260 | "authors": [ | ||
1261 | { | ||
1262 | "name": "A. Grandt", | ||
1263 | "email": "php@grandt.com", | ||
1264 | "homepage": "http://grandt.com", | ||
1265 | "role": "Developer" | ||
1266 | }, | ||
1267 | { | ||
1268 | "name": "Greg Kappatos", | ||
1269 | "homepage": "http://websiteconnect.com.au", | ||
1270 | "role": "Developer" | ||
1271 | } | ||
1272 | ], | ||
1273 | "description": "Merge and stream multiple Zip files on the fly.", | ||
1274 | "homepage": "https://github.com/Grandt/PHPZipMerge", | ||
1275 | "keywords": [ | ||
1276 | "archive", | ||
1277 | "compressed", | ||
1278 | "compression", | ||
1279 | "merge", | ||
1280 | "phpzip", | ||
1281 | "pkzip", | ||
1282 | "stream", | ||
1283 | "zip" | ||
1284 | ], | ||
1285 | "time": "2015-08-18 13:49:33" | ||
1286 | }, | ||
1287 | { | ||
1288 | "name": "grandt/relativepath", | ||
1289 | "version": "1.0.2", | ||
1290 | "source": { | ||
1291 | "type": "git", | ||
1292 | "url": "https://github.com/Grandt/PHPRelativePath.git", | ||
1293 | "reference": "19541133c24143b6295688472c54dd6ed15a5462" | ||
1294 | }, | ||
1295 | "dist": { | ||
1296 | "type": "zip", | ||
1297 | "url": "https://api.github.com/repos/Grandt/PHPRelativePath/zipball/19541133c24143b6295688472c54dd6ed15a5462", | ||
1298 | "reference": "19541133c24143b6295688472c54dd6ed15a5462", | ||
1299 | "shasum": "" | ||
1300 | }, | ||
1301 | "require": { | ||
1302 | "php": ">=5.0" | ||
1303 | }, | ||
1304 | "type": "library", | ||
1305 | "autoload": { | ||
1306 | "classmap": [ | ||
1307 | "RelativePath.php" | ||
1308 | ] | ||
1309 | }, | ||
1310 | "notification-url": "https://packagist.org/downloads/", | ||
1311 | "license": [ | ||
1312 | "LGPL-2.1" | ||
1313 | ], | ||
1314 | "authors": [ | ||
1315 | { | ||
1316 | "name": "A. Grandt", | ||
1317 | "email": "php@grandt.com", | ||
1318 | "role": "Developer" | ||
1319 | } | ||
1320 | ], | ||
1321 | "description": "A class for cleaning up/collapsing relative paths. Like real_path, but without the need for the path to exist on the filesystem.", | ||
1322 | "homepage": "https://github.com/Grandt/PHPRelativePath", | ||
1323 | "keywords": [ | ||
1324 | "file path" | ||
1325 | ], | ||
1326 | "time": "2015-05-14 08:18:23" | ||
1327 | }, | ||
1328 | { | ||
1092 | "name": "guzzlehttp/guzzle", | 1329 | "name": "guzzlehttp/guzzle", |
1093 | "version": "5.3.0", | 1330 | "version": "5.3.0", |
1094 | "source": { | 1331 | "source": { |
@@ -2524,6 +2761,67 @@ | |||
2524 | "time": "2015-07-25 16:39:46" | 2761 | "time": "2015-07-25 16:39:46" |
2525 | }, | 2762 | }, |
2526 | { | 2763 | { |
2764 | "name": "phpzip/phpzip", | ||
2765 | "version": "2.0.7", | ||
2766 | "source": { | ||
2767 | "type": "git", | ||
2768 | "url": "https://github.com/Grandt/PHPZip.git", | ||
2769 | "reference": "a43a7ce8b2f21050f8b143876c5c1661b0d65306" | ||
2770 | }, | ||
2771 | "dist": { | ||
2772 | "type": "zip", | ||
2773 | "url": "https://api.github.com/repos/Grandt/PHPZip/zipball/a43a7ce8b2f21050f8b143876c5c1661b0d65306", | ||
2774 | "reference": "a43a7ce8b2f21050f8b143876c5c1661b0d65306", | ||
2775 | "shasum": "" | ||
2776 | }, | ||
2777 | "require": { | ||
2778 | "grandt/binstring": ">=0.2.0", | ||
2779 | "grandt/phpzipmerge": ">=1.0.3", | ||
2780 | "grandt/relativepath": ">=1.0.1", | ||
2781 | "php": ">=5.3.0" | ||
2782 | }, | ||
2783 | "type": "library", | ||
2784 | "autoload": { | ||
2785 | "psr-4": { | ||
2786 | "PHPZip\\Zip\\": "src/Zip" | ||
2787 | } | ||
2788 | }, | ||
2789 | "notification-url": "https://packagist.org/downloads/", | ||
2790 | "license": [ | ||
2791 | "LGPL-2.1" | ||
2792 | ], | ||
2793 | "authors": [ | ||
2794 | { | ||
2795 | "name": "Adam Schmalhofer", | ||
2796 | "email": "Adam.Schmalhofer@gmx.de", | ||
2797 | "role": "Developer" | ||
2798 | }, | ||
2799 | { | ||
2800 | "name": "A. Grandt", | ||
2801 | "email": "php@grandt.com", | ||
2802 | "homepage": "http://grandt.com", | ||
2803 | "role": "Developer" | ||
2804 | }, | ||
2805 | { | ||
2806 | "name": "Greg Kappatos", | ||
2807 | "homepage": "http://websiteconnect.com.au", | ||
2808 | "role": "Developer" | ||
2809 | } | ||
2810 | ], | ||
2811 | "description": "Package to create and stream archives of compressed files in ZIP format with PHP 5.3+", | ||
2812 | "homepage": "https://github.com/Grandt/PHPZip", | ||
2813 | "keywords": [ | ||
2814 | "archive", | ||
2815 | "compressed", | ||
2816 | "compression", | ||
2817 | "phpzip", | ||
2818 | "pkzip", | ||
2819 | "stream", | ||
2820 | "zip" | ||
2821 | ], | ||
2822 | "time": "2015-04-30 06:45:53" | ||
2823 | }, | ||
2824 | { | ||
2527 | "name": "psr/log", | 2825 | "name": "psr/log", |
2528 | "version": "1.0.0", | 2826 | "version": "1.0.0", |
2529 | "source": { | 2827 | "source": { |
@@ -3497,6 +3795,55 @@ | |||
3497 | "time": "2015-11-05 12:49:06" | 3795 | "time": "2015-11-05 12:49:06" |
3498 | }, | 3796 | }, |
3499 | { | 3797 | { |
3798 | "name": "wallabag/php-mobi", | ||
3799 | "version": "1.0.1", | ||
3800 | "source": { | ||
3801 | "type": "git", | ||
3802 | "url": "https://github.com/wallabag/php-mobi.git", | ||
3803 | "reference": "1cd7d022fe6be838535d6bba917d19cc48dcf487" | ||
3804 | }, | ||
3805 | "dist": { | ||
3806 | "type": "zip", | ||
3807 | "url": "https://api.github.com/repos/wallabag/php-mobi/zipball/1cd7d022fe6be838535d6bba917d19cc48dcf487", | ||
3808 | "reference": "1cd7d022fe6be838535d6bba917d19cc48dcf487", | ||
3809 | "shasum": "" | ||
3810 | }, | ||
3811 | "require": { | ||
3812 | "php": ">=5.3.0" | ||
3813 | }, | ||
3814 | "replace": { | ||
3815 | "wallabag/phpmobi": "*" | ||
3816 | }, | ||
3817 | "type": "library", | ||
3818 | "autoload": { | ||
3819 | "files": [ | ||
3820 | "MOBIClass/MOBI.php" | ||
3821 | ] | ||
3822 | }, | ||
3823 | "license": [ | ||
3824 | "Apache-2.0" | ||
3825 | ], | ||
3826 | "authors": [ | ||
3827 | { | ||
3828 | "name": "Sander Kromwijk", | ||
3829 | "email": "s.kromwijk@gmail.co", | ||
3830 | "role": "Original developer" | ||
3831 | }, | ||
3832 | { | ||
3833 | "name": "Nicolas Lœuillet", | ||
3834 | "email": "nicolas@loeuillet.org", | ||
3835 | "homepage": "http://www.cdetc.fr" | ||
3836 | } | ||
3837 | ], | ||
3838 | "description": "A Mobipocket file (.mobi) creator in PHP.", | ||
3839 | "homepage": "https://github.com/wallabag/phpMobi", | ||
3840 | "support": { | ||
3841 | "source": "https://github.com/wallabag/php-mobi/tree/1.0.1", | ||
3842 | "issues": "https://github.com/wallabag/php-mobi/issues" | ||
3843 | }, | ||
3844 | "time": "2015-10-16 08:42:42" | ||
3845 | }, | ||
3846 | { | ||
3500 | "name": "willdurand/hateoas", | 3847 | "name": "willdurand/hateoas", |
3501 | "version": "v2.6.0", | 3848 | "version": "v2.6.0", |
3502 | "source": { | 3849 | "source": { |
@@ -3602,7 +3949,7 @@ | |||
3602 | ], | 3949 | ], |
3603 | "authors": [ | 3950 | "authors": [ |
3604 | { | 3951 | { |
3605 | "name": "William Durand", | 3952 | "name": "William DURAND", |
3606 | "email": "william.durand1@gmail.com" | 3953 | "email": "william.durand1@gmail.com" |
3607 | } | 3954 | } |
3608 | ], | 3955 | ], |
diff --git a/src/Wallabag/CoreBundle/Controller/ExportController.php b/src/Wallabag/CoreBundle/Controller/ExportController.php new file mode 100644 index 00000000..c8ef49a2 --- /dev/null +++ b/src/Wallabag/CoreBundle/Controller/ExportController.php | |||
@@ -0,0 +1,65 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Wallabag\CoreBundle\Controller; | ||
4 | |||
5 | use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; | ||
6 | use Symfony\Bundle\FrameworkBundle\Controller\Controller; | ||
7 | use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; | ||
8 | use Wallabag\CoreBundle\Entity\Entry; | ||
9 | |||
10 | /** | ||
11 | * The try/catch can be removed once all formats will be implemented. | ||
12 | * Still need implementation: txt. | ||
13 | */ | ||
14 | class ExportController extends Controller | ||
15 | { | ||
16 | /** | ||
17 | * Gets one entry content. | ||
18 | * | ||
19 | * @param Entry $entry | ||
20 | * | ||
21 | * @Route("/export/{id}.{format}", name="export_entry", requirements={ | ||
22 | * "format": "epub|mobi|pdf|json|xml|txt|csv", | ||
23 | * "id": "\d+" | ||
24 | * }) | ||
25 | */ | ||
26 | public function downloadEntryAction(Entry $entry, $format) | ||
27 | { | ||
28 | try { | ||
29 | return $this->get('wallabag_core.helper.entries_export') | ||
30 | ->setEntries($entry) | ||
31 | ->updateTitle('entry') | ||
32 | ->exportAs($format); | ||
33 | } catch (\InvalidArgumentException $e) { | ||
34 | throw new NotFoundHttpException($e->getMessage()); | ||
35 | } | ||
36 | } | ||
37 | |||
38 | /** | ||
39 | * Export all entries for current user. | ||
40 | * | ||
41 | * @Route("/export/{category}.{format}", name="export_entries", requirements={ | ||
42 | * "format": "epub|mobi|pdf|json|xml|txt|csv", | ||
43 | * "category": "all|unread|starred|archive" | ||
44 | * }) | ||
45 | */ | ||
46 | public function downloadEntriesAction($format, $category) | ||
47 | { | ||
48 | $method = ucfirst($category); | ||
49 | $methodBuilder = 'getBuilderFor'.$method.'ByUser'; | ||
50 | $entries = $this->getDoctrine() | ||
51 | ->getRepository('WallabagCoreBundle:Entry') | ||
52 | ->$methodBuilder($this->getUser()->getId()) | ||
53 | ->getQuery() | ||
54 | ->getResult(); | ||
55 | |||
56 | try { | ||
57 | return $this->get('wallabag_core.helper.entries_export') | ||
58 | ->setEntries($entries) | ||
59 | ->updateTitle($method) | ||
60 | ->exportAs($format); | ||
61 | } catch (\InvalidArgumentException $e) { | ||
62 | throw new NotFoundHttpException($e->getMessage()); | ||
63 | } | ||
64 | } | ||
65 | } | ||
diff --git a/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadEntryData.php b/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadEntryData.php index 7e64c5e1..176c529e 100644 --- a/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadEntryData.php +++ b/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadEntryData.php | |||
@@ -19,6 +19,7 @@ class LoadEntryData extends AbstractFixture implements OrderedFixtureInterface | |||
19 | $entry1->setUrl('http://0.0.0.0'); | 19 | $entry1->setUrl('http://0.0.0.0'); |
20 | $entry1->setReadingTime(11); | 20 | $entry1->setReadingTime(11); |
21 | $entry1->setDomainName('domain.io'); | 21 | $entry1->setDomainName('domain.io'); |
22 | $entry1->setMimetype('text/html'); | ||
22 | $entry1->setTitle('test title entry1'); | 23 | $entry1->setTitle('test title entry1'); |
23 | $entry1->setContent('This is my content /o/'); | 24 | $entry1->setContent('This is my content /o/'); |
24 | $entry1->setLanguage('en'); | 25 | $entry1->setLanguage('en'); |
@@ -31,6 +32,7 @@ class LoadEntryData extends AbstractFixture implements OrderedFixtureInterface | |||
31 | $entry2->setUrl('http://0.0.0.0'); | 32 | $entry2->setUrl('http://0.0.0.0'); |
32 | $entry2->setReadingTime(1); | 33 | $entry2->setReadingTime(1); |
33 | $entry2->setDomainName('domain.io'); | 34 | $entry2->setDomainName('domain.io'); |
35 | $entry2->setMimetype('text/html'); | ||
34 | $entry2->setTitle('test title entry2'); | 36 | $entry2->setTitle('test title entry2'); |
35 | $entry2->setContent('This is my content /o/'); | 37 | $entry2->setContent('This is my content /o/'); |
36 | $entry2->setLanguage('fr'); | 38 | $entry2->setLanguage('fr'); |
@@ -43,6 +45,7 @@ class LoadEntryData extends AbstractFixture implements OrderedFixtureInterface | |||
43 | $entry3->setUrl('http://0.0.0.0'); | 45 | $entry3->setUrl('http://0.0.0.0'); |
44 | $entry3->setReadingTime(1); | 46 | $entry3->setReadingTime(1); |
45 | $entry3->setDomainName('domain.io'); | 47 | $entry3->setDomainName('domain.io'); |
48 | $entry3->setMimetype('text/html'); | ||
46 | $entry3->setTitle('test title entry3'); | 49 | $entry3->setTitle('test title entry3'); |
47 | $entry3->setContent('This is my content /o/'); | 50 | $entry3->setContent('This is my content /o/'); |
48 | $entry3->setLanguage('en'); | 51 | $entry3->setLanguage('en'); |
@@ -63,6 +66,7 @@ class LoadEntryData extends AbstractFixture implements OrderedFixtureInterface | |||
63 | $entry4->setUrl('http://0.0.0.0'); | 66 | $entry4->setUrl('http://0.0.0.0'); |
64 | $entry4->setReadingTime(12); | 67 | $entry4->setReadingTime(12); |
65 | $entry4->setDomainName('domain.io'); | 68 | $entry4->setDomainName('domain.io'); |
69 | $entry4->setMimetype('text/html'); | ||
66 | $entry4->setTitle('test title entry4'); | 70 | $entry4->setTitle('test title entry4'); |
67 | $entry4->setContent('This is my content /o/'); | 71 | $entry4->setContent('This is my content /o/'); |
68 | $entry4->setLanguage('en'); | 72 | $entry4->setLanguage('en'); |
@@ -83,6 +87,7 @@ class LoadEntryData extends AbstractFixture implements OrderedFixtureInterface | |||
83 | $entry5->setUrl('http://0.0.0.0'); | 87 | $entry5->setUrl('http://0.0.0.0'); |
84 | $entry5->setReadingTime(12); | 88 | $entry5->setReadingTime(12); |
85 | $entry5->setDomainName('domain.io'); | 89 | $entry5->setDomainName('domain.io'); |
90 | $entry5->setMimetype('text/html'); | ||
86 | $entry5->setTitle('test title entry5'); | 91 | $entry5->setTitle('test title entry5'); |
87 | $entry5->setContent('This is my content /o/'); | 92 | $entry5->setContent('This is my content /o/'); |
88 | $entry5->setStarred(true); | 93 | $entry5->setStarred(true); |
@@ -97,6 +102,7 @@ class LoadEntryData extends AbstractFixture implements OrderedFixtureInterface | |||
97 | $entry6->setUrl('http://0.0.0.0'); | 102 | $entry6->setUrl('http://0.0.0.0'); |
98 | $entry6->setReadingTime(12); | 103 | $entry6->setReadingTime(12); |
99 | $entry6->setDomainName('domain.io'); | 104 | $entry6->setDomainName('domain.io'); |
105 | $entry6->setMimetype('text/html'); | ||
100 | $entry6->setTitle('test title entry6'); | 106 | $entry6->setTitle('test title entry6'); |
101 | $entry6->setContent('This is my content /o/'); | 107 | $entry6->setContent('This is my content /o/'); |
102 | $entry6->setArchived(true); | 108 | $entry6->setArchived(true); |
diff --git a/src/Wallabag/CoreBundle/Entity/Entry.php b/src/Wallabag/CoreBundle/Entity/Entry.php index 9e5446a6..5aa582f8 100644 --- a/src/Wallabag/CoreBundle/Entity/Entry.php +++ b/src/Wallabag/CoreBundle/Entity/Entry.php | |||
@@ -6,6 +6,7 @@ use Doctrine\Common\Collections\ArrayCollection; | |||
6 | use Doctrine\ORM\Mapping as ORM; | 6 | use Doctrine\ORM\Mapping as ORM; |
7 | use Symfony\Component\Validator\Constraints as Assert; | 7 | use Symfony\Component\Validator\Constraints as Assert; |
8 | use Hateoas\Configuration\Annotation as Hateoas; | 8 | use Hateoas\Configuration\Annotation as Hateoas; |
9 | use JMS\Serializer\Annotation\Groups; | ||
9 | use JMS\Serializer\Annotation\XmlRoot; | 10 | use JMS\Serializer\Annotation\XmlRoot; |
10 | use Wallabag\UserBundle\Entity\User; | 11 | use Wallabag\UserBundle\Entity\User; |
11 | 12 | ||
@@ -27,6 +28,8 @@ class Entry | |||
27 | * @ORM\Column(name="id", type="integer") | 28 | * @ORM\Column(name="id", type="integer") |
28 | * @ORM\Id | 29 | * @ORM\Id |
29 | * @ORM\GeneratedValue(strategy="AUTO") | 30 | * @ORM\GeneratedValue(strategy="AUTO") |
31 | * | ||
32 | * @Groups({"entries_for_user", "export_all"}) | ||
30 | */ | 33 | */ |
31 | private $id; | 34 | private $id; |
32 | 35 | ||
@@ -34,6 +37,8 @@ class Entry | |||
34 | * @var string | 37 | * @var string |
35 | * | 38 | * |
36 | * @ORM\Column(name="title", type="text", nullable=true) | 39 | * @ORM\Column(name="title", type="text", nullable=true) |
40 | * | ||
41 | * @Groups({"entries_for_user", "export_all"}) | ||
37 | */ | 42 | */ |
38 | private $title; | 43 | private $title; |
39 | 44 | ||
@@ -42,6 +47,8 @@ class Entry | |||
42 | * | 47 | * |
43 | * @Assert\NotBlank() | 48 | * @Assert\NotBlank() |
44 | * @ORM\Column(name="url", type="text", nullable=true) | 49 | * @ORM\Column(name="url", type="text", nullable=true) |
50 | * | ||
51 | * @Groups({"entries_for_user", "export_all"}) | ||
45 | */ | 52 | */ |
46 | private $url; | 53 | private $url; |
47 | 54 | ||
@@ -49,6 +56,8 @@ class Entry | |||
49 | * @var bool | 56 | * @var bool |
50 | * | 57 | * |
51 | * @ORM\Column(name="is_archived", type="boolean") | 58 | * @ORM\Column(name="is_archived", type="boolean") |
59 | * | ||
60 | * @Groups({"entries_for_user", "export_all"}) | ||
52 | */ | 61 | */ |
53 | private $isArchived = false; | 62 | private $isArchived = false; |
54 | 63 | ||
@@ -56,6 +65,8 @@ class Entry | |||
56 | * @var bool | 65 | * @var bool |
57 | * | 66 | * |
58 | * @ORM\Column(name="is_starred", type="boolean") | 67 | * @ORM\Column(name="is_starred", type="boolean") |
68 | * | ||
69 | * @Groups({"entries_for_user", "export_all"}) | ||
59 | */ | 70 | */ |
60 | private $isStarred = false; | 71 | private $isStarred = false; |
61 | 72 | ||
@@ -63,6 +74,8 @@ class Entry | |||
63 | * @var string | 74 | * @var string |
64 | * | 75 | * |
65 | * @ORM\Column(name="content", type="text", nullable=true) | 76 | * @ORM\Column(name="content", type="text", nullable=true) |
77 | * | ||
78 | * @Groups({"entries_for_user", "export_all"}) | ||
66 | */ | 79 | */ |
67 | private $content; | 80 | private $content; |
68 | 81 | ||
@@ -70,6 +83,8 @@ class Entry | |||
70 | * @var date | 83 | * @var date |
71 | * | 84 | * |
72 | * @ORM\Column(name="created_at", type="datetime") | 85 | * @ORM\Column(name="created_at", type="datetime") |
86 | * | ||
87 | * @Groups({"export_all"}) | ||
73 | */ | 88 | */ |
74 | private $createdAt; | 89 | private $createdAt; |
75 | 90 | ||
@@ -77,6 +92,8 @@ class Entry | |||
77 | * @var date | 92 | * @var date |
78 | * | 93 | * |
79 | * @ORM\Column(name="updated_at", type="datetime") | 94 | * @ORM\Column(name="updated_at", type="datetime") |
95 | * | ||
96 | * @Groups({"export_all"}) | ||
80 | */ | 97 | */ |
81 | private $updatedAt; | 98 | private $updatedAt; |
82 | 99 | ||
@@ -84,6 +101,8 @@ class Entry | |||
84 | * @var string | 101 | * @var string |
85 | * | 102 | * |
86 | * @ORM\Column(name="comments", type="text", nullable=true) | 103 | * @ORM\Column(name="comments", type="text", nullable=true) |
104 | * | ||
105 | * @Groups({"export_all"}) | ||
87 | */ | 106 | */ |
88 | private $comments; | 107 | private $comments; |
89 | 108 | ||
@@ -91,6 +110,8 @@ class Entry | |||
91 | * @var string | 110 | * @var string |
92 | * | 111 | * |
93 | * @ORM\Column(name="mimetype", type="text", nullable=true) | 112 | * @ORM\Column(name="mimetype", type="text", nullable=true) |
113 | * | ||
114 | * @Groups({"entries_for_user", "export_all"}) | ||
94 | */ | 115 | */ |
95 | private $mimetype; | 116 | private $mimetype; |
96 | 117 | ||
@@ -98,6 +119,8 @@ class Entry | |||
98 | * @var string | 119 | * @var string |
99 | * | 120 | * |
100 | * @ORM\Column(name="language", type="text", nullable=true) | 121 | * @ORM\Column(name="language", type="text", nullable=true) |
122 | * | ||
123 | * @Groups({"entries_for_user", "export_all"}) | ||
101 | */ | 124 | */ |
102 | private $language; | 125 | private $language; |
103 | 126 | ||
@@ -105,6 +128,8 @@ class Entry | |||
105 | * @var int | 128 | * @var int |
106 | * | 129 | * |
107 | * @ORM\Column(name="reading_time", type="integer", nullable=true) | 130 | * @ORM\Column(name="reading_time", type="integer", nullable=true) |
131 | * | ||
132 | * @Groups({"entries_for_user", "export_all"}) | ||
108 | */ | 133 | */ |
109 | private $readingTime; | 134 | private $readingTime; |
110 | 135 | ||
@@ -112,6 +137,8 @@ class Entry | |||
112 | * @var string | 137 | * @var string |
113 | * | 138 | * |
114 | * @ORM\Column(name="domain_name", type="text", nullable=true) | 139 | * @ORM\Column(name="domain_name", type="text", nullable=true) |
140 | * | ||
141 | * @Groups({"entries_for_user", "export_all"}) | ||
115 | */ | 142 | */ |
116 | private $domainName; | 143 | private $domainName; |
117 | 144 | ||
@@ -119,6 +146,8 @@ class Entry | |||
119 | * @var string | 146 | * @var string |
120 | * | 147 | * |
121 | * @ORM\Column(name="preview_picture", type="text", nullable=true) | 148 | * @ORM\Column(name="preview_picture", type="text", nullable=true) |
149 | * | ||
150 | * @Groups({"entries_for_user", "export_all"}) | ||
122 | */ | 151 | */ |
123 | private $previewPicture; | 152 | private $previewPicture; |
124 | 153 | ||
@@ -126,17 +155,23 @@ class Entry | |||
126 | * @var bool | 155 | * @var bool |
127 | * | 156 | * |
128 | * @ORM\Column(name="is_public", type="boolean", nullable=true, options={"default" = false}) | 157 | * @ORM\Column(name="is_public", type="boolean", nullable=true, options={"default" = false}) |
158 | * | ||
159 | * @Groups({"export_all"}) | ||
129 | */ | 160 | */ |
130 | private $isPublic; | 161 | private $isPublic; |
131 | 162 | ||
132 | /** | 163 | /** |
133 | * @ORM\ManyToOne(targetEntity="Wallabag\UserBundle\Entity\User", inversedBy="entries") | 164 | * @ORM\ManyToOne(targetEntity="Wallabag\UserBundle\Entity\User", inversedBy="entries") |
165 | * | ||
166 | * @Groups({"export_all"}) | ||
134 | */ | 167 | */ |
135 | private $user; | 168 | private $user; |
136 | 169 | ||
137 | /** | 170 | /** |
138 | * @ORM\ManyToMany(targetEntity="Tag", inversedBy="entries", cascade={"persist"}) | 171 | * @ORM\ManyToMany(targetEntity="Tag", inversedBy="entries", cascade={"persist"}) |
139 | * @ORM\JoinTable | 172 | * @ORM\JoinTable |
173 | * | ||
174 | * @Groups({"entries_for_user", "export_all"}) | ||
140 | */ | 175 | */ |
141 | private $tags; | 176 | private $tags; |
142 | 177 | ||
diff --git a/src/Wallabag/CoreBundle/Helper/EntriesExport.php b/src/Wallabag/CoreBundle/Helper/EntriesExport.php new file mode 100644 index 00000000..d6a4d094 --- /dev/null +++ b/src/Wallabag/CoreBundle/Helper/EntriesExport.php | |||
@@ -0,0 +1,394 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Wallabag\CoreBundle\Helper; | ||
4 | |||
5 | use PHPePub\Core\EPub; | ||
6 | use PHPePub\Core\Structure\OPF\DublinCore; | ||
7 | use Symfony\Component\HttpFoundation\Response; | ||
8 | use JMS\Serializer; | ||
9 | use JMS\Serializer\SerializerBuilder; | ||
10 | use JMS\Serializer\SerializationContext; | ||
11 | |||
12 | /** | ||
13 | * This class doesn't have unit test BUT it's fully covered by a functional test with ExportControllerTest. | ||
14 | */ | ||
15 | class EntriesExport | ||
16 | { | ||
17 | private $wallabagUrl; | ||
18 | private $logoPath; | ||
19 | private $title = ''; | ||
20 | private $entries = array(); | ||
21 | private $authors = array('wallabag'); | ||
22 | private $language = ''; | ||
23 | private $tags = array(); | ||
24 | private $footerTemplate = '<div style="text-align:center;"> | ||
25 | <p>Produced by wallabag with %EXPORT_METHOD%</p> | ||
26 | <p>Please open <a href="https://github.com/wallabag/wallabag/issues">an issue</a> if you have trouble with the display of this E-Book on your device.</p> | ||
27 | </div'; | ||
28 | |||
29 | /** | ||
30 | * @param string $wallabagUrl Wallabag instance url | ||
31 | * @param string $logoPath Path to the logo FROM THE BUNDLE SCOPE | ||
32 | */ | ||
33 | public function __construct($wallabagUrl, $logoPath) | ||
34 | { | ||
35 | $this->wallabagUrl = $wallabagUrl; | ||
36 | $this->logoPath = $logoPath; | ||
37 | } | ||
38 | |||
39 | /** | ||
40 | * Define entries. | ||
41 | * | ||
42 | * @param array|Entry $entries An array of entries or one entry | ||
43 | */ | ||
44 | public function setEntries($entries) | ||
45 | { | ||
46 | if (!is_array($entries)) { | ||
47 | $this->language = $entries->getLanguage(); | ||
48 | $entries = array($entries); | ||
49 | } | ||
50 | |||
51 | $this->entries = $entries; | ||
52 | |||
53 | foreach ($entries as $entry) { | ||
54 | $this->tags[] = $entry->getTags(); | ||
55 | } | ||
56 | |||
57 | return $this; | ||
58 | } | ||
59 | |||
60 | /** | ||
61 | * Sets the category of which we want to get articles, or just one entry. | ||
62 | * | ||
63 | * @param string $method Method to get articles | ||
64 | */ | ||
65 | public function updateTitle($method) | ||
66 | { | ||
67 | $this->title = $method.' articles'; | ||
68 | |||
69 | if ('entry' === $method) { | ||
70 | $this->title = $this->entries[0]->getTitle(); | ||
71 | } | ||
72 | |||
73 | return $this; | ||
74 | } | ||
75 | |||
76 | /** | ||
77 | * Sets the output format. | ||
78 | * | ||
79 | * @param string $format | ||
80 | */ | ||
81 | public function exportAs($format) | ||
82 | { | ||
83 | switch ($format) { | ||
84 | case 'epub': | ||
85 | return $this->produceEpub(); | ||
86 | |||
87 | case 'mobi': | ||
88 | return $this->produceMobi(); | ||
89 | |||
90 | case 'pdf': | ||
91 | return $this->producePDF(); | ||
92 | |||
93 | case 'csv': | ||
94 | return $this->produceCSV(); | ||
95 | |||
96 | case 'json': | ||
97 | return $this->produceJSON(); | ||
98 | |||
99 | case 'xml': | ||
100 | return $this->produceXML(); | ||
101 | } | ||
102 | |||
103 | throw new \InvalidArgumentException(sprintf('The format "%s" is not yet supported.', $format)); | ||
104 | } | ||
105 | |||
106 | /** | ||
107 | * Use PHPePub to dump a .epub file. | ||
108 | */ | ||
109 | private function produceEpub() | ||
110 | { | ||
111 | /* | ||
112 | * Start and End of the book | ||
113 | */ | ||
114 | $content_start = | ||
115 | "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" | ||
116 | ."<html xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:epub=\"http://www.idpf.org/2007/ops\">\n" | ||
117 | .'<head>' | ||
118 | ."<meta http-equiv=\"Default-Style\" content=\"text/html; charset=utf-8\" />\n" | ||
119 | ."<title>wallabag articles book</title>\n" | ||
120 | ."</head>\n" | ||
121 | ."<body>\n"; | ||
122 | |||
123 | $bookEnd = "</body>\n</html>\n"; | ||
124 | |||
125 | $book = new EPub(EPub::BOOK_VERSION_EPUB3); | ||
126 | |||
127 | /* | ||
128 | * Book metadata | ||
129 | */ | ||
130 | |||
131 | $book->setTitle($this->title); | ||
132 | // Could also be the ISBN number, prefered for published books, or a UUID. | ||
133 | $book->setIdentifier($this->title, EPub::IDENTIFIER_URI); | ||
134 | // Not needed, but included for the example, Language is mandatory, but EPub defaults to "en". Use RFC3066 Language codes, such as "en", "da", "fr" etc. | ||
135 | $book->setLanguage($this->language); | ||
136 | $book->setDescription('Some articles saved on my wallabag'); | ||
137 | |||
138 | foreach ($this->authors as $author) { | ||
139 | $book->setAuthor($author, $author); | ||
140 | } | ||
141 | |||
142 | // I hope this is a non existant address :) | ||
143 | $book->setPublisher('wallabag', 'wallabag'); | ||
144 | // Strictly not needed as the book date defaults to time(). | ||
145 | $book->setDate(time()); | ||
146 | $book->setSourceURL($this->wallabagUrl); | ||
147 | |||
148 | $book->addDublinCoreMetadata(DublinCore::CONTRIBUTOR, 'PHP'); | ||
149 | $book->addDublinCoreMetadata(DublinCore::CONTRIBUTOR, 'wallabag'); | ||
150 | |||
151 | /* | ||
152 | * Front page | ||
153 | */ | ||
154 | if (file_exists($this->logoPath)) { | ||
155 | $book->setCoverImage('Cover.png', file_get_contents($this->logoPath), 'image/png'); | ||
156 | } | ||
157 | |||
158 | $book->addChapter('Notices', 'Cover2.html', $content_start.$this->getExportInformation('PHPePub').$bookEnd); | ||
159 | |||
160 | $book->buildTOC(); | ||
161 | |||
162 | /* | ||
163 | * Adding actual entries | ||
164 | */ | ||
165 | |||
166 | // set tags as subjects | ||
167 | foreach ($this->entries as $entry) { | ||
168 | foreach ($this->tags as $tag) { | ||
169 | $book->setSubject($tag['value']); | ||
170 | } | ||
171 | |||
172 | $chapter = $content_start.$entry->getContent().$bookEnd; | ||
173 | $book->addChapter($entry->getTitle(), htmlspecialchars($entry->getTitle()).'.html', $chapter, true, EPub::EXTERNAL_REF_ADD); | ||
174 | } | ||
175 | |||
176 | return Response::create( | ||
177 | $book->getBook(), | ||
178 | 200, | ||
179 | array( | ||
180 | 'Content-Description' => 'File Transfer', | ||
181 | 'Content-type' => 'application/epub+zip', | ||
182 | 'Content-Disposition' => 'attachment; filename="'.$this->title.'.epub"', | ||
183 | 'Content-Transfer-Encoding' => 'binary', | ||
184 | ) | ||
185 | )->send(); | ||
186 | } | ||
187 | |||
188 | /** | ||
189 | * Use PHPMobi to dump a .mobi file. | ||
190 | */ | ||
191 | private function produceMobi() | ||
192 | { | ||
193 | $mobi = new \MOBI(); | ||
194 | $content = new \MOBIFile(); | ||
195 | |||
196 | /* | ||
197 | * Book metadata | ||
198 | */ | ||
199 | $content->set('title', $this->title); | ||
200 | $content->set('author', implode($this->authors)); | ||
201 | $content->set('subject', $this->title); | ||
202 | |||
203 | /* | ||
204 | * Front page | ||
205 | */ | ||
206 | $content->appendParagraph($this->getExportInformation('PHPMobi')); | ||
207 | if (file_exists($this->logoPath)) { | ||
208 | $content->appendImage(imagecreatefrompng($this->logoPath)); | ||
209 | } | ||
210 | $content->appendPageBreak(); | ||
211 | |||
212 | /* | ||
213 | * Adding actual entries | ||
214 | */ | ||
215 | foreach ($this->entries as $entry) { | ||
216 | $content->appendChapterTitle($entry->getTitle()); | ||
217 | $content->appendParagraph($entry->getContent()); | ||
218 | $content->appendPageBreak(); | ||
219 | } | ||
220 | $mobi->setContentProvider($content); | ||
221 | |||
222 | // the browser inside Kindle Devices doesn't likes special caracters either, we limit to A-z/0-9 | ||
223 | $this->title = preg_replace('/[^A-Za-z0-9\-]/', '', $this->title); | ||
224 | |||
225 | return Response::create( | ||
226 | $mobi->toString(), | ||
227 | 200, | ||
228 | array( | ||
229 | 'Accept-Ranges' => 'bytes', | ||
230 | 'Content-Description' => 'File Transfer', | ||
231 | 'Content-type' => 'application/x-mobipocket-ebook', | ||
232 | 'Content-Disposition' => 'attachment; filename="'.$this->title.'.mobi"', | ||
233 | 'Content-Transfer-Encoding' => 'binary', | ||
234 | ) | ||
235 | )->send(); | ||
236 | } | ||
237 | |||
238 | /** | ||
239 | * Use TCPDF to dump a .pdf file. | ||
240 | */ | ||
241 | private function producePDF() | ||
242 | { | ||
243 | $pdf = new \TCPDF(PDF_PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT, true, 'UTF-8', false); | ||
244 | |||
245 | /* | ||
246 | * Book metadata | ||
247 | */ | ||
248 | $pdf->SetCreator(PDF_CREATOR); | ||
249 | $pdf->SetAuthor('wallabag'); | ||
250 | $pdf->SetTitle($this->title); | ||
251 | $pdf->SetSubject('Articles via wallabag'); | ||
252 | $pdf->SetKeywords('wallabag'); | ||
253 | |||
254 | /* | ||
255 | * Front page | ||
256 | */ | ||
257 | $pdf->AddPage(); | ||
258 | $intro = '<h1>'.$this->title.'</h1>'.$this->getExportInformation('tcpdf'); | ||
259 | |||
260 | $pdf->writeHTMLCell(0, 0, '', '', $intro, 0, 1, 0, true, '', true); | ||
261 | |||
262 | /* | ||
263 | * Adding actual entries | ||
264 | */ | ||
265 | foreach ($this->entries as $entry) { | ||
266 | foreach ($this->tags as $tag) { | ||
267 | $pdf->SetKeywords($tag['value']); | ||
268 | } | ||
269 | |||
270 | $pdf->AddPage(); | ||
271 | $html = '<h1>'.$entry->getTitle().'</h1>'; | ||
272 | $html .= $entry->getContent(); | ||
273 | |||
274 | $pdf->writeHTMLCell(0, 0, '', '', $html, 0, 1, 0, true, '', true); | ||
275 | } | ||
276 | |||
277 | // set image scale factor | ||
278 | $pdf->setImageScale(PDF_IMAGE_SCALE_RATIO); | ||
279 | |||
280 | return Response::create( | ||
281 | $pdf->Output('', 'S'), | ||
282 | 200, | ||
283 | array( | ||
284 | 'Content-Description' => 'File Transfer', | ||
285 | 'Content-type' => 'application/pdf', | ||
286 | 'Content-Disposition' => 'attachment; filename="'.$this->title.'.pdf"', | ||
287 | 'Content-Transfer-Encoding' => 'binary', | ||
288 | ) | ||
289 | )->send(); | ||
290 | } | ||
291 | |||
292 | /** | ||
293 | * Inspired from CsvFileDumper. | ||
294 | */ | ||
295 | private function produceCSV() | ||
296 | { | ||
297 | $delimiter = ';'; | ||
298 | $enclosure = '"'; | ||
299 | $handle = fopen('php://memory', 'rb+'); | ||
300 | |||
301 | fputcsv($handle, array('Title', 'URL', 'Content', 'Tags', 'MIME Type', 'Language'), $delimiter, $enclosure); | ||
302 | |||
303 | foreach ($this->entries as $entry) { | ||
304 | fputcsv( | ||
305 | $handle, | ||
306 | array( | ||
307 | $entry->getTitle(), | ||
308 | $entry->getURL(), | ||
309 | // remove new line to avoid crazy results | ||
310 | str_replace(array("\r\n", "\r", "\n"), '', $entry->getContent()), | ||
311 | implode(', ', $entry->getTags()->toArray()), | ||
312 | $entry->getMimetype(), | ||
313 | $entry->getLanguage(), | ||
314 | ), | ||
315 | $delimiter, | ||
316 | $enclosure | ||
317 | ); | ||
318 | } | ||
319 | |||
320 | rewind($handle); | ||
321 | $output = stream_get_contents($handle); | ||
322 | fclose($handle); | ||
323 | |||
324 | return Response::create( | ||
325 | $output, | ||
326 | 200, | ||
327 | array( | ||
328 | 'Content-type' => 'application/csv', | ||
329 | 'Content-Disposition' => 'attachment; filename="'.$this->title.'.csv"', | ||
330 | 'Content-Transfer-Encoding' => 'UTF-8', | ||
331 | ) | ||
332 | )->send(); | ||
333 | } | ||
334 | |||
335 | private function produceJSON() | ||
336 | { | ||
337 | return Response::create( | ||
338 | $this->prepareSerializingContent('json'), | ||
339 | 200, | ||
340 | array( | ||
341 | 'Content-type' => 'application/json', | ||
342 | 'Content-Disposition' => 'attachment; filename="'.$this->title.'.json"', | ||
343 | 'Content-Transfer-Encoding' => 'UTF-8', | ||
344 | ) | ||
345 | )->send(); | ||
346 | } | ||
347 | |||
348 | private function produceXML() | ||
349 | { | ||
350 | return Response::create( | ||
351 | $this->prepareSerializingContent('xml'), | ||
352 | 200, | ||
353 | array( | ||
354 | 'Content-type' => 'application/xml', | ||
355 | 'Content-Disposition' => 'attachment; filename="'.$this->title.'.xml"', | ||
356 | 'Content-Transfer-Encoding' => 'UTF-8', | ||
357 | ) | ||
358 | )->send(); | ||
359 | } | ||
360 | |||
361 | /** | ||
362 | * Return a Serializer object for producing processes that need it (JSON & XML). | ||
363 | * | ||
364 | * @return Serializer | ||
365 | */ | ||
366 | private function prepareSerializingContent($format) | ||
367 | { | ||
368 | $serializer = SerializerBuilder::create()->build(); | ||
369 | |||
370 | return $serializer->serialize( | ||
371 | $this->entries, | ||
372 | $format, | ||
373 | SerializationContext::create()->setGroups(array('entries_for_user')) | ||
374 | ); | ||
375 | } | ||
376 | |||
377 | /** | ||
378 | * Return a kind of footer / information for the epub. | ||
379 | * | ||
380 | * @param string $type Generator of the export, can be: tdpdf, PHPePub, PHPMobi | ||
381 | * | ||
382 | * @return string | ||
383 | */ | ||
384 | private function getExportInformation($type) | ||
385 | { | ||
386 | $info = str_replace('%EXPORT_METHOD%', $type, $this->footerTemplate); | ||
387 | |||
388 | if ('tcpdf' === $type) { | ||
389 | return str_replace('%IMAGE%', '<img src="'.$this->logoPath.'" />', $info); | ||
390 | } | ||
391 | |||
392 | return str_replace('%IMAGE%', '', $info); | ||
393 | } | ||
394 | } | ||
diff --git a/src/Wallabag/CoreBundle/Resources/config/services.yml b/src/Wallabag/CoreBundle/Resources/config/services.yml index 65c2c8d8..8e21b052 100644 --- a/src/Wallabag/CoreBundle/Resources/config/services.yml +++ b/src/Wallabag/CoreBundle/Resources/config/services.yml | |||
@@ -64,3 +64,9 @@ services: | |||
64 | - %language% | 64 | - %language% |
65 | tags: | 65 | tags: |
66 | - { name: kernel.event_subscriber } | 66 | - { name: kernel.event_subscriber } |
67 | |||
68 | wallabag_core.helper.entries_export: | ||
69 | class: Wallabag\CoreBundle\Helper\EntriesExport | ||
70 | arguments: | ||
71 | - %wallabag_url% | ||
72 | - src/Wallabag/CoreBundle/Resources/views/themes/_global/public/img/appicon/apple-touch-icon-152.png | ||
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/entries.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/entries.html.twig index 668824bc..bf38bff8 100644 --- a/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/entries.html.twig +++ b/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/entries.html.twig | |||
@@ -91,6 +91,24 @@ | |||
91 | {% endfor %} | 91 | {% endfor %} |
92 | </ul> | 92 | </ul> |
93 | 93 | ||
94 | <!-- Export --> | ||
95 | <div id="export" class="side-nav fixed right-aligned"> | ||
96 | {% set currentRoute = app.request.attributes.get('_route') %} | ||
97 | {% if currentRoute == 'homepage' %} | ||
98 | {% set currentRoute = 'unread' %} | ||
99 | {% endif %} | ||
100 | <h4 class="center">{% trans %}Export{% endtrans %}</h4> | ||
101 | <ul> | ||
102 | <li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'epub' }) }}">{% trans %}EPUB{% endtrans %}</a></li> | ||
103 | <li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'mobi' }) }}">{% trans %}MOBI{% endtrans %}</a></li> | ||
104 | <li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'pdf' }) }}">{% trans %}PDF{% endtrans %}</a></li> | ||
105 | <li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'xml' }) }}">{% trans %}XML{% endtrans %}</a></li> | ||
106 | <li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'json' }) }}">{% trans %}JSON{% endtrans %}</a></li> | ||
107 | <li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'csv' }) }}">{% trans %}CSV{% endtrans %}</a></li> | ||
108 | <li class="bold"><del><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'txt' }) }}">{% trans %}TXT{% endtrans %}</a></del></li> | ||
109 | </ul> | ||
110 | </div> | ||
111 | |||
94 | <!-- Filters --> | 112 | <!-- Filters --> |
95 | <div id="filters" class="side-nav fixed right-aligned"> | 113 | <div id="filters" class="side-nav fixed right-aligned"> |
96 | <form action="{{ path('all') }}"> | 114 | <form action="{{ path('all') }}"> |
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/entry.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/entry.html.twig index 7230506c..fd84d984 100644 --- a/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/entry.html.twig +++ b/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/entry.html.twig | |||
@@ -102,13 +102,16 @@ | |||
102 | <li class="bold"> | 102 | <li class="bold"> |
103 | <a class="waves-effect collapsible-header"> | 103 | <a class="waves-effect collapsible-header"> |
104 | <i class="mdi-file-file-download small"></i> | 104 | <i class="mdi-file-file-download small"></i> |
105 | <span><del>{% trans %}Download{% endtrans %}</del></span> | 105 | <span>{% trans %}Download{% endtrans %}</span> |
106 | </a> | 106 | </a> |
107 | <div class="collapsible-body"> | 107 | <div class="collapsible-body"> |
108 | <ul> | 108 | <ul> |
109 | {% if export_epub %}<li><del><a href="?epub&method=id&value={{ entry.id }}" title="Generate ePub file">EPUB</a></del></li>{% endif %} | 109 | {% if export_epub %}<li><a href="{{ path('export_entry', { 'id': entry.id, 'format': 'epub' }) }}" title="Generate ePub file">EPUB</a></li>{% endif %} |
110 | {% if export_mobi %}<li><del><a href="?mobi&method=id&value={{ entry.id }}" title="Generate Mobi file">MOBI</a></del></li>{% endif %} | 110 | {% if export_mobi %}<li><a href="{{ path('export_entry', { 'id': entry.id, 'format': 'mobi' }) }}" title="Generate Mobi file">MOBI</a></li>{% endif %} |
111 | {% if export_pdf %}<li><del><a href="?pdf&method=id&value={{ entry.id }}" title="Generate PDF file">PDF</a></del> </li>{% endif %} | 111 | {% if export_pdf %}<li><a href="{{ path('export_entry', { 'id': entry.id, 'format': 'pdf' }) }}" title="Generate PDF file">PDF</a></li>{% endif %} |
112 | <li><a href="{{ path('export_entry', { 'id': entry.id, 'format': 'csv' }) }}" title="Generate CSV file">CSV</a></li> | ||
113 | <li><a href="{{ path('export_entry', { 'id': entry.id, 'format': 'json' }) }}" title="Generate JSON file">JSON</a></li> | ||
114 | <li><a href="{{ path('export_entry', { 'id': entry.id, 'format': 'xml' }) }}" title="Generate XML file">XML</a></li> | ||
112 | </ul> | 115 | </ul> |
113 | </div> | 116 | </div> |
114 | </li> | 117 | </li> |
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/material/layout.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/material/layout.html.twig index 95b3977c..f426e25b 100644 --- a/src/Wallabag/CoreBundle/Resources/views/themes/material/layout.html.twig +++ b/src/Wallabag/CoreBundle/Resources/views/themes/material/layout.html.twig | |||
@@ -59,6 +59,7 @@ | |||
59 | <li class="bold"><a title="{% trans %}Add a new entry{% endtrans %}" class="waves-effect" href="{{ path('new') }}" id="nav-btn-add"><i class="mdi-content-add"></i></a></li> | 59 | <li class="bold"><a title="{% trans %}Add a new entry{% endtrans %}" class="waves-effect" href="{{ path('new') }}" id="nav-btn-add"><i class="mdi-content-add"></i></a></li> |
60 | <li><a title="{% trans %}Search{% endtrans %}" class="waves-effect" href="javascript: void(null);" id="nav-btn-search"><i class="mdi-action-search"></i></a> | 60 | <li><a title="{% trans %}Search{% endtrans %}" class="waves-effect" href="javascript: void(null);" id="nav-btn-search"><i class="mdi-action-search"></i></a> |
61 | <li id="button_filters"><a title="{% trans %}Filter entries{% endtrans %}" href="#" data-activates="filters" class="nav-panel-menu button-collapse-right"><i class="mdi-content-filter-list"></i></a></li> | 61 | <li id="button_filters"><a title="{% trans %}Filter entries{% endtrans %}" href="#" data-activates="filters" class="nav-panel-menu button-collapse-right"><i class="mdi-content-filter-list"></i></a></li> |
62 | <li id="button_export"><a title="{% trans %}Export{% endtrans %}" class="nav-panel-menu button-collapse-right" href="#" data-activates="export" class="nav-panel-menu button-collapse-right"><i class="mdi-file-file-download"></i></a></li> | ||
62 | </ul> | 63 | </ul> |
63 | </div> | 64 | </div> |
64 | <form method="get" action="index.php"> | 65 | <form method="get" action="index.php"> |
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/material/public/js/init.js b/src/Wallabag/CoreBundle/Resources/views/themes/material/public/js/init.js index edfdee82..491a7916 100755 --- a/src/Wallabag/CoreBundle/Resources/views/themes/material/public/js/init.js +++ b/src/Wallabag/CoreBundle/Resources/views/themes/material/public/js/init.js | |||
@@ -11,6 +11,14 @@ function init_filters() { | |||
11 | } | 11 | } |
12 | } | 12 | } |
13 | 13 | ||
14 | function init_export() { | ||
15 | // no display if export not aviable | ||
16 | if ($("div").is("#export")) { | ||
17 | $('#button_export').show(); | ||
18 | $('.button-collapse-right').sideNav({ edge: 'right' }); | ||
19 | } | ||
20 | } | ||
21 | |||
14 | $(document).ready(function(){ | 22 | $(document).ready(function(){ |
15 | // sideNav | 23 | // sideNav |
16 | $('.button-collapse').sideNav(); | 24 | $('.button-collapse').sideNav(); |
@@ -26,6 +34,7 @@ $(document).ready(function(){ | |||
26 | format: 'dd/mm/yyyy', | 34 | format: 'dd/mm/yyyy', |
27 | }); | 35 | }); |
28 | init_filters(); | 36 | init_filters(); |
37 | init_export(); | ||
29 | 38 | ||
30 | $('#nav-btn-add-tag').on('click', function(){ | 39 | $('#nav-btn-add-tag').on('click', function(){ |
31 | $(".nav-panel-add-tag").toggle(100); | 40 | $(".nav-panel-add-tag").toggle(100); |
diff --git a/src/Wallabag/CoreBundle/Tests/Controller/ExportControllerTest.php b/src/Wallabag/CoreBundle/Tests/Controller/ExportControllerTest.php new file mode 100644 index 00000000..739b2dec --- /dev/null +++ b/src/Wallabag/CoreBundle/Tests/Controller/ExportControllerTest.php | |||
@@ -0,0 +1,234 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Wallabag\CoreBundle\Tests\Controller; | ||
4 | |||
5 | use Wallabag\CoreBundle\Tests\WallabagCoreTestCase; | ||
6 | |||
7 | class ExportControllerTest extends WallabagCoreTestCase | ||
8 | { | ||
9 | public function testLogin() | ||
10 | { | ||
11 | $client = $this->getClient(); | ||
12 | |||
13 | $client->request('GET', '/export/unread.csv'); | ||
14 | |||
15 | $this->assertEquals(302, $client->getResponse()->getStatusCode()); | ||
16 | $this->assertContains('login', $client->getResponse()->headers->get('location')); | ||
17 | } | ||
18 | |||
19 | public function testUnknownCategoryExport() | ||
20 | { | ||
21 | $this->logInAs('admin'); | ||
22 | $client = $this->getClient(); | ||
23 | |||
24 | $client->request('GET', '/export/awesomeness.epub'); | ||
25 | |||
26 | $this->assertEquals(404, $client->getResponse()->getStatusCode()); | ||
27 | } | ||
28 | |||
29 | public function testUnknownFormatExport() | ||
30 | { | ||
31 | $this->logInAs('admin'); | ||
32 | $client = $this->getClient(); | ||
33 | |||
34 | $client->request('GET', '/export/unread.xslx'); | ||
35 | |||
36 | $this->assertEquals(404, $client->getResponse()->getStatusCode()); | ||
37 | } | ||
38 | |||
39 | public function testUnsupportedFormatExport() | ||
40 | { | ||
41 | $this->logInAs('admin'); | ||
42 | $client = $this->getClient(); | ||
43 | |||
44 | $client->request('GET', '/export/unread.txt'); | ||
45 | $this->assertEquals(404, $client->getResponse()->getStatusCode()); | ||
46 | |||
47 | $content = $client->getContainer() | ||
48 | ->get('doctrine.orm.entity_manager') | ||
49 | ->getRepository('WallabagCoreBundle:Entry') | ||
50 | ->findOneByUsernameAndNotArchived('admin'); | ||
51 | |||
52 | $client->request('GET', '/export/'.$content->getId().'.txt'); | ||
53 | $this->assertEquals(404, $client->getResponse()->getStatusCode()); | ||
54 | } | ||
55 | |||
56 | public function testBadEntryId() | ||
57 | { | ||
58 | $this->logInAs('admin'); | ||
59 | $client = $this->getClient(); | ||
60 | |||
61 | $client->request('GET', '/export/0.mobi'); | ||
62 | |||
63 | $this->assertEquals(404, $client->getResponse()->getStatusCode()); | ||
64 | } | ||
65 | |||
66 | public function testEpubExport() | ||
67 | { | ||
68 | $this->logInAs('admin'); | ||
69 | $client = $this->getClient(); | ||
70 | |||
71 | ob_start(); | ||
72 | $crawler = $client->request('GET', '/export/archive.epub'); | ||
73 | ob_end_clean(); | ||
74 | |||
75 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); | ||
76 | |||
77 | $headers = $client->getResponse()->headers; | ||
78 | $this->assertEquals('application/epub+zip', $headers->get('content-type')); | ||
79 | $this->assertEquals('attachment; filename="Archive articles.epub"', $headers->get('content-disposition')); | ||
80 | $this->assertEquals('binary', $headers->get('content-transfer-encoding')); | ||
81 | } | ||
82 | |||
83 | public function testMobiExport() | ||
84 | { | ||
85 | $this->logInAs('admin'); | ||
86 | $client = $this->getClient(); | ||
87 | |||
88 | $content = $client->getContainer() | ||
89 | ->get('doctrine.orm.entity_manager') | ||
90 | ->getRepository('WallabagCoreBundle:Entry') | ||
91 | ->findOneByUsernameAndNotArchived('admin'); | ||
92 | |||
93 | ob_start(); | ||
94 | $crawler = $client->request('GET', '/export/'.$content->getId().'.mobi'); | ||
95 | ob_end_clean(); | ||
96 | |||
97 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); | ||
98 | |||
99 | $headers = $client->getResponse()->headers; | ||
100 | $this->assertEquals('application/x-mobipocket-ebook', $headers->get('content-type')); | ||
101 | $this->assertEquals('attachment; filename="'.preg_replace('/[^A-Za-z0-9\-]/', '', $content->getTitle()).'.mobi"', $headers->get('content-disposition')); | ||
102 | $this->assertEquals('binary', $headers->get('content-transfer-encoding')); | ||
103 | } | ||
104 | |||
105 | public function testPdfExport() | ||
106 | { | ||
107 | $this->logInAs('admin'); | ||
108 | $client = $this->getClient(); | ||
109 | |||
110 | ob_start(); | ||
111 | $crawler = $client->request('GET', '/export/all.pdf'); | ||
112 | ob_end_clean(); | ||
113 | |||
114 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); | ||
115 | |||
116 | $headers = $client->getResponse()->headers; | ||
117 | $this->assertEquals('application/pdf', $headers->get('content-type')); | ||
118 | $this->assertEquals('attachment; filename="All articles.pdf"', $headers->get('content-disposition')); | ||
119 | $this->assertEquals('binary', $headers->get('content-transfer-encoding')); | ||
120 | } | ||
121 | |||
122 | public function testCsvExport() | ||
123 | { | ||
124 | $this->logInAs('admin'); | ||
125 | $client = $this->getClient(); | ||
126 | |||
127 | // to be sure results are the same | ||
128 | $contentInDB = $client->getContainer() | ||
129 | ->get('doctrine.orm.entity_manager') | ||
130 | ->getRepository('WallabagCoreBundle:Entry') | ||
131 | ->createQueryBuilder('e') | ||
132 | ->leftJoin('e.user', 'u') | ||
133 | ->where('u.username = :username')->setParameter('username', 'admin') | ||
134 | ->andWhere('e.isArchived = true') | ||
135 | ->getQuery() | ||
136 | ->getArrayResult(); | ||
137 | |||
138 | ob_start(); | ||
139 | $crawler = $client->request('GET', '/export/archive.csv'); | ||
140 | ob_end_clean(); | ||
141 | |||
142 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); | ||
143 | |||
144 | $headers = $client->getResponse()->headers; | ||
145 | $this->assertEquals('application/csv', $headers->get('content-type')); | ||
146 | $this->assertEquals('attachment; filename="Archive articles.csv"', $headers->get('content-disposition')); | ||
147 | $this->assertEquals('UTF-8', $headers->get('content-transfer-encoding')); | ||
148 | |||
149 | $csv = str_getcsv($client->getResponse()->getContent(), "\n"); | ||
150 | |||
151 | $this->assertGreaterThan(1, $csv); | ||
152 | // +1 for title line | ||
153 | $this->assertEquals(count($contentInDB)+1, count($csv)); | ||
154 | $this->assertEquals('Title;URL;Content;Tags;"MIME Type";Language', $csv[0]); | ||
155 | } | ||
156 | |||
157 | public function testJsonExport() | ||
158 | { | ||
159 | $this->logInAs('admin'); | ||
160 | $client = $this->getClient(); | ||
161 | |||
162 | // to be sure results are the same | ||
163 | $contentInDB = $client->getContainer() | ||
164 | ->get('doctrine.orm.entity_manager') | ||
165 | ->getRepository('WallabagCoreBundle:Entry') | ||
166 | ->createQueryBuilder('e') | ||
167 | ->leftJoin('e.user', 'u') | ||
168 | ->where('u.username = :username')->setParameter('username', 'admin') | ||
169 | ->getQuery() | ||
170 | ->getArrayResult(); | ||
171 | |||
172 | ob_start(); | ||
173 | $crawler = $client->request('GET', '/export/all.json'); | ||
174 | ob_end_clean(); | ||
175 | |||
176 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); | ||
177 | |||
178 | $headers = $client->getResponse()->headers; | ||
179 | $this->assertEquals('application/json', $headers->get('content-type')); | ||
180 | $this->assertEquals('attachment; filename="All articles.json"', $headers->get('content-disposition')); | ||
181 | $this->assertEquals('UTF-8', $headers->get('content-transfer-encoding')); | ||
182 | |||
183 | $content = json_decode($client->getResponse()->getContent(), true); | ||
184 | $this->assertEquals(count($contentInDB), count($content)); | ||
185 | $this->assertArrayHasKey('id', $content[0]); | ||
186 | $this->assertArrayHasKey('title', $content[0]); | ||
187 | $this->assertArrayHasKey('url', $content[0]); | ||
188 | $this->assertArrayHasKey('is_archived', $content[0]); | ||
189 | $this->assertArrayHasKey('is_starred', $content[0]); | ||
190 | $this->assertArrayHasKey('content', $content[0]); | ||
191 | $this->assertArrayHasKey('mimetype', $content[0]); | ||
192 | $this->assertArrayHasKey('language', $content[0]); | ||
193 | $this->assertArrayHasKey('reading_time', $content[0]); | ||
194 | $this->assertArrayHasKey('domain_name', $content[0]); | ||
195 | $this->assertArrayHasKey('tags', $content[0]); | ||
196 | } | ||
197 | |||
198 | public function testXmlExport() | ||
199 | { | ||
200 | $this->logInAs('admin'); | ||
201 | $client = $this->getClient(); | ||
202 | |||
203 | // to be sure results are the same | ||
204 | $contentInDB = $client->getContainer() | ||
205 | ->get('doctrine.orm.entity_manager') | ||
206 | ->getRepository('WallabagCoreBundle:Entry') | ||
207 | ->createQueryBuilder('e') | ||
208 | ->leftJoin('e.user', 'u') | ||
209 | ->where('u.username = :username')->setParameter('username', 'admin') | ||
210 | ->andWhere('e.isArchived = false') | ||
211 | ->getQuery() | ||
212 | ->getArrayResult(); | ||
213 | |||
214 | ob_start(); | ||
215 | $crawler = $client->request('GET', '/export/unread.xml'); | ||
216 | ob_end_clean(); | ||
217 | |||
218 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); | ||
219 | |||
220 | $headers = $client->getResponse()->headers; | ||
221 | $this->assertEquals('application/xml', $headers->get('content-type')); | ||
222 | $this->assertEquals('attachment; filename="Unread articles.xml"', $headers->get('content-disposition')); | ||
223 | $this->assertEquals('UTF-8', $headers->get('content-transfer-encoding')); | ||
224 | |||
225 | $content = new \SimpleXMLElement($client->getResponse()->getContent()); | ||
226 | $this->assertGreaterThan(0, $content->count()); | ||
227 | $this->assertEquals(count($contentInDB), $content->count()); | ||
228 | $this->assertNotEmpty('id', (string) $content->entry[0]->id); | ||
229 | $this->assertNotEmpty('title', (string) $content->entry[0]->title); | ||
230 | $this->assertNotEmpty('url', (string) $content->entry[0]->url); | ||
231 | $this->assertNotEmpty('content', (string) $content->entry[0]->content); | ||
232 | $this->assertNotEmpty('domain_name', (string) $content->entry[0]->domain_name); | ||
233 | } | ||
234 | } | ||