From 78657347c5b463d7c22bfc8c87b7db39fe058833 Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Wed, 17 Jun 2020 19:08:02 +0200 Subject: [PATCH] Process bookmarks import through Slim controller --- .../controller/admin/ExportController.php | 17 +- .../controller/admin/ImportController.php | 81 ++++++++++ .../netscape/NetscapeBookmarkUtils.php | 15 +- doc/md/Translations.md | 2 +- index.php | 48 +----- .../controller/admin/ExportControllerTest.php | 6 +- .../controller/admin/ImportControllerTest.php | 148 ++++++++++++++++++ tests/netscape/BookmarkImportTest.php | 15 +- tpl/default/import.html | 2 +- tpl/default/tools.html | 2 +- tpl/vintage/import.html | 2 +- tpl/vintage/tools.html | 2 +- 12 files changed, 256 insertions(+), 84 deletions(-) create mode 100644 application/front/controller/admin/ImportController.php create mode 100644 tests/front/controller/admin/ImportControllerTest.php diff --git a/application/front/controller/admin/ExportController.php b/application/front/controller/admin/ExportController.php index 8e0e5a56..7afbfc23 100644 --- a/application/front/controller/admin/ExportController.php +++ b/application/front/controller/admin/ExportController.php @@ -33,6 +33,8 @@ class ExportController extends ShaarliAdminController */ public function export(Request $request, Response $response): Response { + $this->checkToken($request); + $selection = $request->getParam('selection'); if (empty($selection)) { @@ -74,19 +76,4 @@ class ExportController extends ShaarliAdminController 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/application/front/controller/admin/ImportController.php b/application/front/controller/admin/ImportController.php new file mode 100644 index 00000000..8c5305b9 --- /dev/null +++ b/application/front/controller/admin/ImportController.php @@ -0,0 +1,81 @@ +assignView( + 'maxfilesize', + get_max_upload_size( + ini_get('post_max_size'), + ini_get('upload_max_filesize'), + false + ) + ); + $this->assignView( + 'maxfilesizeHuman', + get_max_upload_size( + ini_get('post_max_size'), + ini_get('upload_max_filesize'), + true + ) + ); + $this->assignView('pagetitle', t('Import') .' - '. $this->container->conf->get('general.title', 'Shaarli')); + + return $response->write($this->render('import')); + } + + /** + * POST /admin/import - Process import file provided and create bookmarks + */ + public function import(Request $request, Response $response): Response + { + $this->checkToken($request); + + $file = ($request->getUploadedFiles() ?? [])['filetoupload'] ?? null; + if (!$file instanceof UploadedFileInterface) { + $this->saveErrorMessage(t('No import file provided.')); + + return $this->redirect($response, '/admin/import'); + } + + + // Import bookmarks from an uploaded file + if (0 === $file->getSize()) { + // The file is too big or some form field may be missing. + $msg = sprintf( + t( + 'The file you are trying to upload is probably bigger than what this webserver can accept' + .' (%s). Please upload in smaller chunks.' + ), + get_max_upload_size(ini_get('post_max_size'), ini_get('upload_max_filesize')) + ); + $this->saveErrorMessage($msg); + + return $this->redirect($response, '/admin/import'); + } + + $status = $this->container->netscapeBookmarkUtils->import($request->getParams(), $file); + + $this->saveSuccessMessage($status); + + return $this->redirect($response, '/admin/import'); + } +} diff --git a/application/netscape/NetscapeBookmarkUtils.php b/application/netscape/NetscapeBookmarkUtils.php index 8557cca2..b150f649 100644 --- a/application/netscape/NetscapeBookmarkUtils.php +++ b/application/netscape/NetscapeBookmarkUtils.php @@ -6,6 +6,7 @@ use DateTime; use DateTimeZone; use Exception; use Katzgrau\KLogger\Logger; +use Psr\Http\Message\UploadedFileInterface; use Psr\Log\LogLevel; use Shaarli\Bookmark\Bookmark; use Shaarli\Bookmark\BookmarkServiceInterface; @@ -79,20 +80,20 @@ class NetscapeBookmarkUtils /** * Imports Web bookmarks from an uploaded Netscape bookmark dump * - * @param array $post Server $_POST parameters - * @param array $files Server $_FILES parameters + * @param array $post Server $_POST parameters + * @param UploadedFileInterface $file File in PSR-7 object format * * @return string Summary of the bookmark import status */ - public function import($post, $files) + public function import($post, UploadedFileInterface $file) { $start = time(); - $filename = $files['filetoupload']['name']; - $filesize = $files['filetoupload']['size']; - $data = file_get_contents($files['filetoupload']['tmp_name']); + $filename = $file->getClientFilename(); + $filesize = $file->getSize(); + $data = (string) $file->getStream(); if (preg_match('//i', $data) === 0) { - return self::importStatus($filename, $filesize); + return $this->importStatus($filename, $filesize); } // Overwrite existing bookmarks? diff --git a/doc/md/Translations.md b/doc/md/Translations.md index af2c3daa..df86f4d4 100644 --- a/doc/md/Translations.md +++ b/doc/md/Translations.md @@ -40,7 +40,7 @@ http:///admin/tools http:///daily http:///?post http:///admin/export -http:///?do=import +http:///admin/import http:///login http:///picture-wall http:///?do=pluginadmin diff --git a/index.php b/index.php index 030fdfa3..47fef3ed 100644 --- a/index.php +++ b/index.php @@ -578,51 +578,7 @@ function renderPage($conf, $pluginManager, $bookmarkService, $history, $sessionM } if ($targetPage == Router::$PAGE_IMPORT) { - // Upload a Netscape bookmark dump to import its contents - - if (! isset($_POST['token']) || ! isset($_FILES['filetoupload'])) { - // Show import dialog - $PAGE->assign( - 'maxfilesize', - get_max_upload_size( - ini_get('post_max_size'), - ini_get('upload_max_filesize'), - false - ) - ); - $PAGE->assign( - 'maxfilesizeHuman', - get_max_upload_size( - ini_get('post_max_size'), - ini_get('upload_max_filesize'), - true - ) - ); - $PAGE->assign('pagetitle', t('Import') .' - '. $conf->get('general.title', 'Shaarli')); - $PAGE->renderPage('import'); - exit; - } - - // Import bookmarks from an uploaded file - if (isset($_FILES['filetoupload']['size']) && $_FILES['filetoupload']['size'] == 0) { - // The file is too big or some form field may be missing. - $msg = sprintf( - t( - 'The file you are trying to upload is probably bigger than what this webserver can accept' - .' (%s). Please upload in smaller chunks.' - ), - get_max_upload_size(ini_get('post_max_size'), ini_get('upload_max_filesize')) - ); - echo ''; - exit; - } - if (! $sessionManager->checkToken($_POST['token'])) { - die('Wrong token.'); - } - $netscapeBookmarkUtils = new NetscapeBookmarkUtils($bookmarkService, $conf, $history); - $status = $netscapeBookmarkUtils->import($_POST, $_FILES); - echo ''; + header('Location: ./admin/import'); exit; } @@ -1064,6 +1020,8 @@ $app->group('', function () { $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('/admin/import', '\Shaarli\Front\Controller\Admin\ImportController:index'); + $this->post('/admin/import', '\Shaarli\Front\Controller\Admin\ImportController:import'); $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 index e43a9626..50d9e378 100644 --- a/tests/front/controller/admin/ExportControllerTest.php +++ b/tests/front/controller/admin/ExportControllerTest.php @@ -2,14 +2,12 @@ declare(strict_types=1); -namespace front\controller\admin; +namespace Shaarli\Front\Controller\Admin; use PHPUnit\Framework\TestCase; use Shaarli\Bookmark\Bookmark; use Shaarli\Formatter\BookmarkFormatter; use Shaarli\Formatter\BookmarkRawFormatter; -use Shaarli\Front\Controller\Admin\ExportController; -use Shaarli\Front\Controller\Admin\FrontAdminControllerMockHelper; use Shaarli\Netscape\NetscapeBookmarkUtils; use Shaarli\Security\SessionManager; use Slim\Http\Request; @@ -117,7 +115,6 @@ class ExportControllerTest extends TestCase $request = $this->createMock(Request::class); $response = new Response(); - $this->container->sessionManager = $this->createMock(SessionManager::class); $this->container->sessionManager ->expects(static::once()) ->method('setSessionParameter') @@ -152,7 +149,6 @@ class ExportControllerTest extends TestCase ->willThrowException(new \Exception($message = 'error message')); ; - $this->container->sessionManager = $this->createMock(SessionManager::class); $this->container->sessionManager ->expects(static::once()) ->method('setSessionParameter') diff --git a/tests/front/controller/admin/ImportControllerTest.php b/tests/front/controller/admin/ImportControllerTest.php new file mode 100644 index 00000000..eb31fad0 --- /dev/null +++ b/tests/front/controller/admin/ImportControllerTest.php @@ -0,0 +1,148 @@ +createContainer(); + + $this->controller = new ImportController($this->container); + } + + /** + * Test displaying import 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('import', (string) $result->getBody()); + + static::assertSame('Import - Shaarli', $assignedVariables['pagetitle']); + static::assertIsInt($assignedVariables['maxfilesize']); + static::assertRegExp('/\d+[KM]iB/', $assignedVariables['maxfilesizeHuman']); + } + + /** + * Test importing a file with default and valid parameters + */ + public function testImportDefault(): void + { + $parameters = [ + 'abc' => 'def', + 'other' => 'param', + ]; + + $requestFile = new UploadedFile('file', 'name', 'type', 123); + + $request = $this->createMock(Request::class); + $request->method('getParams')->willReturnCallback(function () use ($parameters) { + return $parameters; + }); + $request->method('getUploadedFiles')->willReturn(['filetoupload' => $requestFile]); + $response = new Response(); + + $this->container->netscapeBookmarkUtils = $this->createMock(NetscapeBookmarkUtils::class); + $this->container->netscapeBookmarkUtils + ->expects(static::once()) + ->method('import') + ->willReturnCallback( + function ( + array $post, + UploadedFileInterface $file + ) use ($parameters, $requestFile): string { + static::assertSame($parameters, $post); + static::assertSame($requestFile, $file); + + return 'status'; + } + ) + ; + + $this->container->sessionManager + ->expects(static::once()) + ->method('setSessionParameter') + ->with(SessionManager::KEY_SUCCESS_MESSAGES, ['status']) + ; + + $result = $this->controller->import($request, $response); + + static::assertSame(302, $result->getStatusCode()); + static::assertSame(['/subfolder/admin/import'], $result->getHeader('location')); + } + + /** + * Test posting an import request - without import file + */ + public function testImportFileMissing(): void + { + $request = $this->createMock(Request::class); + $response = new Response(); + + $this->container->sessionManager + ->expects(static::once()) + ->method('setSessionParameter') + ->with(SessionManager::KEY_ERROR_MESSAGES, ['No import file provided.']) + ; + + $result = $this->controller->import($request, $response); + + static::assertSame(302, $result->getStatusCode()); + static::assertSame(['/subfolder/admin/import'], $result->getHeader('location')); + } + + /** + * Test posting an import request - with an empty file + */ + public function testImportEmptyFile(): void + { + $requestFile = new UploadedFile('file', 'name', 'type', 0); + + $request = $this->createMock(Request::class); + $request->method('getUploadedFiles')->willReturn(['filetoupload' => $requestFile]); + $response = new Response(); + + $this->container->netscapeBookmarkUtils = $this->createMock(NetscapeBookmarkUtils::class); + $this->container->netscapeBookmarkUtils->expects(static::never())->method('filterAndFormat'); + + $this->container->sessionManager + ->expects(static::once()) + ->method('setSessionParameter') + ->willReturnCallback(function (string $key, array $value): SessionManager { + static::assertSame(SessionManager::KEY_ERROR_MESSAGES, $key); + static::assertStringStartsWith('The file you are trying to upload is probably bigger', $value[0]); + + return $this->container->sessionManager; + }) + ; + + $result = $this->controller->import($request, $response); + + static::assertSame(302, $result->getStatusCode()); + static::assertSame(['/subfolder/admin/import'], $result->getHeader('location')); + } +} diff --git a/tests/netscape/BookmarkImportTest.php b/tests/netscape/BookmarkImportTest.php index 20b1c6f4..f678e26b 100644 --- a/tests/netscape/BookmarkImportTest.php +++ b/tests/netscape/BookmarkImportTest.php @@ -4,27 +4,28 @@ namespace Shaarli\Netscape; use DateTime; use PHPUnit\Framework\TestCase; +use Psr\Http\Message\UploadedFileInterface; use Shaarli\Bookmark\Bookmark; use Shaarli\Bookmark\BookmarkFileService; use Shaarli\Bookmark\BookmarkFilter; use Shaarli\Config\ConfigManager; use Shaarli\History; +use Slim\Http\UploadedFile; /** * Utility function to load a file's metadata in a $_FILES-like array * * @param string $filename Basename of the file * - * @return array A $_FILES-like array + * @return UploadedFileInterface Upload file in PSR-7 compatible object */ function file2array($filename) { - return array( - 'filetoupload' => array( - 'name' => $filename, - 'tmp_name' => __DIR__ . '/input/' . $filename, - 'size' => filesize(__DIR__ . '/input/' . $filename) - ) + return new UploadedFile( + __DIR__ . '/input/' . $filename, + $filename, + null, + filesize(__DIR__ . '/input/' . $filename) ); } diff --git a/tpl/default/import.html b/tpl/default/import.html index 97203d93..156de71f 100644 --- a/tpl/default/import.html +++ b/tpl/default/import.html @@ -6,7 +6,7 @@ {include="page.header"} -
+
diff --git a/tpl/default/tools.html b/tpl/default/tools.html index fa007460..045defc9 100644 --- a/tpl/default/tools.html +++ b/tpl/default/tools.html @@ -33,7 +33,7 @@
- {'Import links'|t} diff --git a/tpl/vintage/import.html b/tpl/vintage/import.html index a2e37751..7d6eac76 100644 --- a/tpl/vintage/import.html +++ b/tpl/vintage/import.html @@ -6,7 +6,7 @@ {include="page.header"}
Import Netscape HTML bookmarks (as exported from Firefox/Chrome/Opera/Delicious/Diigo...) (Max: {$maxfilesize}). - diff --git a/tpl/vintage/tools.html b/tpl/vintage/tools.html index 67ebd175..95f89d8c 100644 --- a/tpl/vintage/tools.html +++ b/tpl/vintage/tools.html @@ -13,7 +13,7 @@

{/if} Rename/delete tags: Rename or delete a tag in all links

- Import: Import Netscape html bookmarks (as exported from Firefox, Chrome, Opera, delicious...) + Import: Import Netscape html bookmarks (as exported from Firefox, Chrome, Opera, delicious...)

Export: Export Netscape html bookmarks (which can be imported in Firefox, Chrome, Opera, delicious...)

-- 2.41.0