]> git.immae.eu Git - github/shaarli/Shaarli.git/commitdiff
Process bookmarks import through Slim controller
authorArthurHoaro <arthur@hoa.ro>
Wed, 17 Jun 2020 17:08:02 +0000 (19:08 +0200)
committerArthurHoaro <arthur@hoa.ro>
Thu, 23 Jul 2020 19:19:21 +0000 (21:19 +0200)
12 files changed:
application/front/controller/admin/ExportController.php
application/front/controller/admin/ImportController.php [new file with mode: 0644]
application/netscape/NetscapeBookmarkUtils.php
doc/md/Translations.md
index.php
tests/front/controller/admin/ExportControllerTest.php
tests/front/controller/admin/ImportControllerTest.php [new file with mode: 0644]
tests/netscape/BookmarkImportTest.php
tpl/default/import.html
tpl/default/tools.html
tpl/vintage/import.html
tpl/vintage/tools.html

index 8e0e5a561ab4af83b127c13e1740f513edd16056..7afbfc2389c3b07876a8886ef2397dcc72904e5e 100644 (file)
@@ -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 (file)
index 0000000..8c5305b
--- /dev/null
@@ -0,0 +1,81 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Shaarli\Front\Controller\Admin;
+
+use Psr\Http\Message\UploadedFileInterface;
+use Slim\Http\Request;
+use Slim\Http\Response;
+
+/**
+ * Class ImportController
+ *
+ * Slim controller used to display Shaarli data import page,
+ * and import bookmarks from Netscape Bookmarks file.
+ */
+class ImportController extends ShaarliAdminController
+{
+    /**
+     * GET /admin/import - Display import page
+     */
+    public function index(Request $request, Response $response): Response
+    {
+        $this->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');
+    }
+}
index 8557cca2f3ed0228ccf4907191172420f5a4b05c..b150f64927d332e0023749b7e9f7426d2a7eb3a5 100644 (file)
@@ -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('/<!DOCTYPE NETSCAPE-Bookmark-file-1>/i', $data) === 0) {
-            return self::importStatus($filename, $filesize);
+            return $this->importStatus($filename, $filesize);
         }
 
         // Overwrite existing bookmarks?
index af2c3daa08077f5a0c8caeb305e424eef04c25e6..df86f4d4b4060ba2b94b50f1fe2204fc77974039 100644 (file)
@@ -40,7 +40,7 @@ http://<replace_domain>/admin/tools
 http://<replace_domain>/daily
 http://<replace_domain>/?post
 http://<replace_domain>/admin/export
-http://<replace_domain>/?do=import
+http://<replace_domain>/admin/import
 http://<replace_domain>/login
 http://<replace_domain>/picture-wall
 http://<replace_domain>/?do=pluginadmin
index 030fdfa3132512694773831e5af19f1475c60683..47fef3ed6a89ab5d18c9e46f45b1f4c8869df2ef 100644 (file)
--- 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 '<script>alert("'. $msg .'");document.location=\'./?do='.Router::$PAGE_IMPORT .'\';</script>';
-            exit;
-        }
-        if (! $sessionManager->checkToken($_POST['token'])) {
-            die('Wrong token.');
-        }
-        $netscapeBookmarkUtils = new NetscapeBookmarkUtils($bookmarkService, $conf, $history);
-        $status = $netscapeBookmarkUtils->import($_POST, $_FILES);
-        echo '<script>alert("'.$status.'");document.location=\'./?do='
-             .Router::$PAGE_IMPORT .'\';</script>';
+        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');
index e43a9626c8a18eb8f4c46b8ffa5fb070588ea352..50d9e378752f890024c8ee59e89ebd1f3120be4a 100644 (file)
@@ -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 (file)
index 0000000..eb31fad
--- /dev/null
@@ -0,0 +1,148 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Shaarli\Front\Controller\Admin;
+
+use PHPUnit\Framework\TestCase;
+use Psr\Http\Message\UploadedFileInterface;
+use Shaarli\Netscape\NetscapeBookmarkUtils;
+use Shaarli\Security\SessionManager;
+use Slim\Http\Request;
+use Slim\Http\Response;
+use Slim\Http\UploadedFile;
+
+class ImportControllerTest extends TestCase
+{
+    use FrontAdminControllerMockHelper;
+
+    /** @var ImportController */
+    protected $controller;
+
+    public function setUp(): void
+    {
+        $this->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'));
+    }
+}
index 20b1c6f404c1d2ccc9505e8ea7336281ebb8d71f..f678e26bd5ca8d3199242d33f6613bb36f7afecc 100644 (file)
@@ -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)
     );
 }
 
index 97203d93073a780554d908559861b2136ac7102e..156de71fc34d790c8400902a3c7944464a639a21 100644 (file)
@@ -6,7 +6,7 @@
 <body>
 {include="page.header"}
 
-<form method="POST" action="{$base_path}/?do=import" enctype="multipart/form-data" name="uploadform" id="uploadform">
+<form method="POST" action="{$base_path}/admin/import" enctype="multipart/form-data" name="uploadform" id="uploadform">
   <div class="pure-g">
     <div class="pure-u-lg-1-4 pure-u-1-24"></div>
     <div class="pure-u-lg-1-2 pure-u-22-24 page-form page-form-complete">
index fa00746062464b68b2906053b483e19b2bbc2793..045defc9efbb02073a7d773eb8beba5e6008426d 100644 (file)
@@ -33,7 +33,7 @@
       </a>
     </div>
     <div class="tools-item">
-      <a href="{$base_path}/?do=import"
+      <a href="{$base_path}/admin/import"
          title="{'Import Netscape HTML bookmarks (as exported from Firefox, Chrome, Opera, delicious...)'|t}">
         <span class="pure-button pure-u-lg-2-3 pure-u-3-4">{'Import links'|t}</span>
       </a>
index a2e377510d43a5d333d215e03d24f2e55b433155..7d6eac766d47677044608434e82d06fdcb9b456c 100644 (file)
@@ -6,7 +6,7 @@
   {include="page.header"}
   <div id="uploaddiv">
     Import Netscape HTML bookmarks (as exported from Firefox/Chrome/Opera/Delicious/Diigo...) (Max: {$maxfilesize}).
-    <form method="POST" action="{$base_path}/?do=import" enctype="multipart/form-data"
+    <form method="POST" action="{$base_path}/admin/import" enctype="multipart/form-data"
           name="uploadform" id="uploadform">
       <input type="hidden" name="token" value="{$token}">
       <input type="hidden" name="MAX_FILE_SIZE" value="{$maxfilesize}">
index 67ebd1751563802f3d26a26c293b8cf0f0efc638..95f89d8c9641e6e41b639233add0616264b42e1b 100644 (file)
@@ -13,7 +13,7 @@
     <br><br>{/if}
                <a href="{$base_path}/admin/tags"><b>Rename/delete tags</b><span>: Rename or delete a tag in all links</span></a>
     <br><br>
-               <a href="{$base_path}/?do=import"><b>Import</b><span>: Import Netscape html bookmarks (as exported from Firefox, Chrome, Opera, delicious...)</span></a>
+               <a href="{$base_path}/admin/import"><b>Import</b><span>: Import Netscape html bookmarks (as exported from Firefox, Chrome, Opera, delicious...)</span></a>
     <br><br>
                <a href="{$base_path}/admin/export"><b>Export</b><span>: Export Netscape html bookmarks (which can be imported in Firefox, Chrome, Opera, delicious...)</span></a>
     <br><br>