From c70ff64a61d62cc8d35a62f30596ecc2a3c578a3 Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Wed, 17 Jun 2020 16:04:18 +0200 Subject: [PATCH 1/1] Process bookmark exports through Slim controllers --- .../controller/admin/ExportController.php | 92 ++++++++++ doc/md/Translations.md | 2 +- index.php | 47 +---- .../controller/admin/ExportControllerTest.php | 167 ++++++++++++++++++ tpl/default/export.html | 3 +- tpl/default/tools.html | 2 +- tpl/vintage/export.html | 3 +- tpl/vintage/tools.html | 2 +- 8 files changed, 267 insertions(+), 51 deletions(-) create mode 100644 application/front/controller/admin/ExportController.php create mode 100644 tests/front/controller/admin/ExportControllerTest.php diff --git a/application/front/controller/admin/ExportController.php b/application/front/controller/admin/ExportController.php new file mode 100644 index 00000000..8e0e5a56 --- /dev/null +++ b/application/front/controller/admin/ExportController.php @@ -0,0 +1,92 @@ +assignView('pagetitle', t('Export') .' - '. $this->container->conf->get('general.title', 'Shaarli')); + + return $response->write($this->render('export')); + } + + /** + * POST /admin/export - Process export, and serve download file named + * bookmarks_(all|private|public)_datetime.html + */ + public function export(Request $request, Response $response): Response + { + $selection = $request->getParam('selection'); + + if (empty($selection)) { + $this->saveErrorMessage(t('Please select an export mode.')); + + return $this->redirect($response, '/admin/export'); + } + + $prependNoteUrl = filter_var($request->getParam('prepend_note_url') ?? false, FILTER_VALIDATE_BOOLEAN); + + try { + $formatter = $this->container->formatterFactory->getFormatter('raw'); + + $this->assignView( + 'links', + $this->container->netscapeBookmarkUtils->filterAndFormat( + $formatter, + $selection, + $prependNoteUrl, + index_url($this->container->environment) + ) + ); + } catch (\Exception $exc) { + $this->saveErrorMessage($exc->getMessage()); + + return $this->redirect($response, '/admin/export'); + } + + $now = new DateTime(); + $response = $response->withHeader('Content-Type', 'text/html; charset=utf-8'); + $response = $response->withHeader( + 'Content-disposition', + 'attachment; filename=bookmarks_'.$selection.'_'.$now->format(Bookmark::LINK_DATE_FORMAT).'.html' + ); + + $this->assignView('date', $now->format(DateTime::RFC822)); + $this->assignView('eol', PHP_EOL); + $this->assignView('selection', $selection); + + return $response->write($this->render('export.bookmarks')); + } + + /** + * @param mixed[] $data Variables passed to the template engine + * + * @return mixed[] Template data after active plugins render_picwall hook execution. + */ + protected function executeHooks(array $data): array + { + $this->container->pluginManager->executeHooks( + 'render_tools', + $data + ); + + return $data; + } +} diff --git a/doc/md/Translations.md b/doc/md/Translations.md index 75eeed7d..af2c3daa 100644 --- a/doc/md/Translations.md +++ b/doc/md/Translations.md @@ -39,7 +39,7 @@ http:///admin/configure http:///admin/tools http:///daily http:///?post -http:///?do=export +http:///admin/export http:///?do=import http:///login http:///picture-wall diff --git a/index.php b/index.php index 7c49bc8d..030fdfa3 100644 --- a/index.php +++ b/index.php @@ -573,50 +573,7 @@ function renderPage($conf, $pluginManager, $bookmarkService, $history, $sessionM } if ($targetPage == Router::$PAGE_EXPORT) { - // Export bookmarks as a Netscape Bookmarks file - - if (empty($_GET['selection'])) { - $PAGE->assign('pagetitle', t('Export') .' - '. $conf->get('general.title', 'Shaarli')); - $PAGE->renderPage('export'); - exit; - } - - // export as bookmarks_(all|private|public)_YYYYmmdd_HHMMSS.html - $selection = $_GET['selection']; - if (isset($_GET['prepend_note_url'])) { - $prependNoteUrl = $_GET['prepend_note_url']; - } else { - $prependNoteUrl = false; - } - - try { - $factory = new FormatterFactory($conf, $loginManager->isLoggedIn()); - $formatter = $factory->getFormatter('raw'); - $PAGE->assign( - 'links', - NetscapeBookmarkUtils::filterAndFormat( - $bookmarkService, - $formatter, - $selection, - $prependNoteUrl, - index_url($_SERVER) - ) - ); - } catch (Exception $exc) { - header('Content-Type: text/plain; charset=utf-8'); - echo $exc->getMessage(); - exit; - } - $now = new DateTime(); - header('Content-Type: text/html; charset=utf-8'); - header( - 'Content-disposition: attachment; filename=bookmarks_' - .$selection.'_'.$now->format(Bookmark::LINK_DATE_FORMAT).'.html' - ); - $PAGE->assign('date', $now->format(DateTime::RFC822)); - $PAGE->assign('eol', PHP_EOL); - $PAGE->assign('selection', $selection); - $PAGE->renderPage('export.bookmarks'); + header('Location: ./admin/export'); exit; } @@ -1105,6 +1062,8 @@ $app->group('', function () { $this->get('/admin/shaare/delete', '\Shaarli\Front\Controller\Admin\ManageShaareController:deleteBookmark'); $this->get('/admin/shaare/visibility', '\Shaarli\Front\Controller\Admin\ManageShaareController:changeVisibility'); $this->get('/admin/shaare/{id:[0-9]+}/pin', '\Shaarli\Front\Controller\Admin\ManageShaareController:pinBookmark'); + $this->get('/admin/export', '\Shaarli\Front\Controller\Admin\ExportController:index'); + $this->post('/admin/export', '\Shaarli\Front\Controller\Admin\ExportController:export'); $this->get('/links-per-page', '\Shaarli\Front\Controller\Admin\SessionFilterController:linksPerPage'); $this->get('/visibility/{visibility}', '\Shaarli\Front\Controller\Admin\SessionFilterController:visibility'); diff --git a/tests/front/controller/admin/ExportControllerTest.php b/tests/front/controller/admin/ExportControllerTest.php new file mode 100644 index 00000000..e43a9626 --- /dev/null +++ b/tests/front/controller/admin/ExportControllerTest.php @@ -0,0 +1,167 @@ +createContainer(); + + $this->controller = new ExportController($this->container); + } + + /** + * Test displaying export page + */ + public function testIndex(): void + { + $assignedVariables = []; + $this->assignTemplateVars($assignedVariables); + + $request = $this->createMock(Request::class); + $response = new Response(); + + $result = $this->controller->index($request, $response); + + static::assertSame(200, $result->getStatusCode()); + static::assertSame('export', (string) $result->getBody()); + + static::assertSame('Export - Shaarli', $assignedVariables['pagetitle']); + } + + /** + * Test posting an export request + */ + public function testExportDefault(): void + { + $assignedVariables = []; + $this->assignTemplateVars($assignedVariables); + + $parameters = [ + 'selection' => 'all', + 'prepend_note_url' => 'on', + ]; + + $request = $this->createMock(Request::class); + $request->method('getParam')->willReturnCallback(function (string $key) use ($parameters) { + return $parameters[$key] ?? null; + }); + $response = new Response(); + + $bookmarks = [ + (new Bookmark())->setUrl('http://link1.tld')->setTitle('Title 1'), + (new Bookmark())->setUrl('http://link2.tld')->setTitle('Title 2'), + ]; + + $this->container->netscapeBookmarkUtils = $this->createMock(NetscapeBookmarkUtils::class); + $this->container->netscapeBookmarkUtils + ->expects(static::once()) + ->method('filterAndFormat') + ->willReturnCallback( + function ( + BookmarkFormatter $formatter, + string $selection, + bool $prependNoteUrl, + string $indexUrl + ) use ($parameters, $bookmarks): array { + static::assertInstanceOf(BookmarkRawFormatter::class, $formatter); + static::assertSame($parameters['selection'], $selection); + static::assertTrue($prependNoteUrl); + static::assertSame('http://shaarli', $indexUrl); + + return $bookmarks; + } + ) + ; + + $result = $this->controller->export($request, $response); + + static::assertSame(200, $result->getStatusCode()); + static::assertSame('export.bookmarks', (string) $result->getBody()); + static::assertSame(['text/html; charset=utf-8'], $result->getHeader('content-type')); + static::assertRegExp( + '/attachment; filename=bookmarks_all_[\d]{8}_[\d]{6}\.html/', + $result->getHeader('content-disposition')[0] + ); + + static::assertNotEmpty($assignedVariables['date']); + static::assertSame(PHP_EOL, $assignedVariables['eol']); + static::assertSame('all', $assignedVariables['selection']); + static::assertSame($bookmarks, $assignedVariables['links']); + } + + /** + * Test posting an export request - without selection parameter + */ + public function testExportSelectionMissing(): void + { + $request = $this->createMock(Request::class); + $response = new Response(); + + $this->container->sessionManager = $this->createMock(SessionManager::class); + $this->container->sessionManager + ->expects(static::once()) + ->method('setSessionParameter') + ->with(SessionManager::KEY_ERROR_MESSAGES, ['Please select an export mode.']) + ; + + $result = $this->controller->export($request, $response); + + static::assertSame(302, $result->getStatusCode()); + static::assertSame(['/subfolder/admin/export'], $result->getHeader('location')); + } + + /** + * Test posting an export request - without selection parameter + */ + public function testExportErrorEncountered(): void + { + $parameters = [ + 'selection' => 'all', + ]; + + $request = $this->createMock(Request::class); + $request->method('getParam')->willReturnCallback(function (string $key) use ($parameters) { + return $parameters[$key] ?? null; + }); + $response = new Response(); + + $this->container->netscapeBookmarkUtils = $this->createMock(NetscapeBookmarkUtils::class); + $this->container->netscapeBookmarkUtils + ->expects(static::once()) + ->method('filterAndFormat') + ->willThrowException(new \Exception($message = 'error message')); + ; + + $this->container->sessionManager = $this->createMock(SessionManager::class); + $this->container->sessionManager + ->expects(static::once()) + ->method('setSessionParameter') + ->with(SessionManager::KEY_ERROR_MESSAGES, [$message]) + ; + + $result = $this->controller->export($request, $response); + + static::assertSame(302, $result->getStatusCode()); + static::assertSame(['/subfolder/admin/export'], $result->getHeader('location')); + } +} diff --git a/tpl/default/export.html b/tpl/default/export.html index 91cf78b6..c9c92943 100644 --- a/tpl/default/export.html +++ b/tpl/default/export.html @@ -6,14 +6,13 @@ {include="page.header"} -
+

{"Export Database"|t}

-
diff --git a/tpl/default/tools.html b/tpl/default/tools.html index d07dabd0..fa007460 100644 --- a/tpl/default/tools.html +++ b/tpl/default/tools.html @@ -39,7 +39,7 @@
- {'Export database'|t} diff --git a/tpl/vintage/export.html b/tpl/vintage/export.html index 67c3d05f..feee307c 100644 --- a/tpl/vintage/export.html +++ b/tpl/vintage/export.html @@ -5,8 +5,7 @@