--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+namespace Shaarli\Front\Controller\Admin;
+
+use DateTime;
+use Shaarli\Bookmark\Bookmark;
+use Slim\Http\Request;
+use Slim\Http\Response;
+
+/**
+ * Class ExportController
+ *
+ * Slim controller used to display Shaarli data export page,
+ * and process the bookmarks export as a Netscape Bookmarks file.
+ */
+class ExportController extends ShaarliAdminController
+{
+ /**
+ * GET /admin/export - Display export page
+ */
+ public function index(Request $request, Response $response): Response
+ {
+ $this->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;
+ }
+}
http://<replace_domain>/admin/tools
http://<replace_domain>/daily
http://<replace_domain>/?post
-http://<replace_domain>/?do=export
+http://<replace_domain>/admin/export
http://<replace_domain>/?do=import
http://<replace_domain>/login
http://<replace_domain>/picture-wall
}
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;
}
$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');
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+namespace 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;
+use Slim\Http\Response;
+
+class ExportControllerTest extends TestCase
+{
+ use FrontAdminControllerMockHelper;
+
+ /** @var ExportController */
+ protected $controller;
+
+ public function setUp(): void
+ {
+ $this->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'));
+ }
+}
<body>
{include="page.header"}
-<form method="GET" action="{$base_path}/?do=export" name="exportform" id="exportform">
+<form method="POST" action="{$base_path}/admin/export" name="exportform" id="exportform">
<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">
<div>
<h2 class="window-title">{"Export Database"|t}</h2>
</div>
- <input type="hidden" name="do" value="export">
<input type="hidden" name="token" value="{$token}">
<div class="pure-g">
</a>
</div>
<div class="tools-item">
- <a href="{$base_path}/?do=export"
+ <a href="{$base_path}/admin/export"
title="{'Export Netscape HTML bookmarks (which can be imported in Firefox, Chrome, Opera, delicious...)'|t}">
<span class="pure-button pure-u-lg-2-3 pure-u-3-4">{'Export database'|t}</span>
</a>
<div id="pageheader">
{include="page.header"}
<div id="toolsdiv">
- <form method="GET">
- <input type="hidden" name="do" value="export">
+ <form method="POST" action="{$base_path}/admin/export">
Selection:<br>
<input type="radio" name="selection" value="all" checked="true"> All<br>
<input type="radio" name="selection" value="private"> Private<br>
<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>
<br><br>
- <a href="{$base_path}/?do=export"><b>Export</b><span>: Export Netscape html bookmarks (which can be imported in Firefox, Chrome, Opera, delicious...)</span></a>
+ <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>
<a class="smallbutton"
onclick="return alertBookmarklet();"