From 485b168a9677d160b0c0426e4f282b9bd0c632c1 Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Sun, 26 Jan 2020 11:15:15 +0100 Subject: [PATCH] Process picwall rendering through Slim controller + UT --- .editorconfig | 2 +- application/bookmark/Bookmark.php | 2 +- application/container/ContainerBuilder.php | 5 + application/container/ShaarliContainer.php | 2 + .../controllers/PictureWallController.php | 72 +++++++ .../ThumbnailsDisabledException.php | 15 ++ application/updater/Updater.php | 18 +- doc/md/RSS-feeds.md | 4 +- doc/md/Translations.md | 2 +- index.php | 33 +--- tests/container/ContainerBuilderTest.php | 4 + .../controller/PictureWallControllerTest.php | 180 ++++++++++++++++++ tpl/default/page.header.html | 2 +- tpl/default/picwall.html | 83 ++++---- tpl/vintage/page.header.html | 2 +- 15 files changed, 342 insertions(+), 84 deletions(-) create mode 100644 application/front/controllers/PictureWallController.php create mode 100644 application/front/exceptions/ThumbnailsDisabledException.php create mode 100644 tests/front/controller/PictureWallControllerTest.php diff --git a/.editorconfig b/.editorconfig index 34bd7994..c2ab80eb 100644 --- a/.editorconfig +++ b/.editorconfig @@ -14,7 +14,7 @@ indent_size = 4 indent_size = 2 [*.php] -max_line_length = 100 +max_line_length = 120 [Dockerfile] max_line_length = 80 diff --git a/application/bookmark/Bookmark.php b/application/bookmark/Bookmark.php index f9b21d3d..83ddab82 100644 --- a/application/bookmark/Bookmark.php +++ b/application/bookmark/Bookmark.php @@ -346,7 +346,7 @@ class Bookmark /** * Get the Thumbnail. * - * @return string|bool + * @return string|bool|null */ public function getThumbnail() { diff --git a/application/container/ContainerBuilder.php b/application/container/ContainerBuilder.php index e2c78ccc..99c12334 100644 --- a/application/container/ContainerBuilder.php +++ b/application/container/ContainerBuilder.php @@ -7,6 +7,7 @@ namespace Shaarli\Container; use Shaarli\Bookmark\BookmarkFileService; use Shaarli\Bookmark\BookmarkServiceInterface; use Shaarli\Config\ConfigManager; +use Shaarli\Formatter\FormatterFactory; use Shaarli\History; use Shaarli\Plugin\PluginManager; use Shaarli\Render\PageBuilder; @@ -76,6 +77,10 @@ class ContainerBuilder return new PluginManager($container->conf); }; + $container['formatterFactory'] = function (ShaarliContainer $container): FormatterFactory { + return new FormatterFactory($container->conf, $container->loginManager->isLoggedIn()); + }; + return $container; } } diff --git a/application/container/ShaarliContainer.php b/application/container/ShaarliContainer.php index 3fa9116e..fdf2f77f 100644 --- a/application/container/ShaarliContainer.php +++ b/application/container/ShaarliContainer.php @@ -6,6 +6,7 @@ namespace Shaarli\Container; use Shaarli\Bookmark\BookmarkServiceInterface; use Shaarli\Config\ConfigManager; +use Shaarli\Formatter\FormatterFactory; use Shaarli\History; use Shaarli\Plugin\PluginManager; use Shaarli\Render\PageBuilder; @@ -23,6 +24,7 @@ use Slim\Container; * @property BookmarkServiceInterface $bookmarkService * @property PageBuilder $pageBuilder * @property PluginManager $pluginManager + * @property FormatterFactory $formatterFactory */ class ShaarliContainer extends Container { diff --git a/application/front/controllers/PictureWallController.php b/application/front/controllers/PictureWallController.php new file mode 100644 index 00000000..08d31b29 --- /dev/null +++ b/application/front/controllers/PictureWallController.php @@ -0,0 +1,72 @@ +container->conf->get('thumbnails.mode', Thumbnailer::MODE_NONE) === Thumbnailer::MODE_NONE) { + throw new ThumbnailsDisabledException(); + } + + $this->assignView( + 'pagetitle', + t('Picture wall') .' - '. $this->container->conf->get('general.title', 'Shaarli') + ); + + // Optionally filter the results: + $links = $this->container->bookmarkService->search($request->getQueryParams()); + $linksToDisplay = []; + + // Get only bookmarks which have a thumbnail. + // Note: we do not retrieve thumbnails here, the request is too heavy. + $formatter = $this->container->formatterFactory->getFormatter('raw'); + foreach ($links as $key => $link) { + if (!empty($link->getThumbnail())) { + $linksToDisplay[] = $formatter->format($link); + } + } + + $data = $this->executeHooks($linksToDisplay); + foreach ($data as $key => $value) { + $this->assignView($key, $value); + } + + return $response->write($this->render('picwall')); + } + + /** + * @param mixed[] $linksToDisplay List of formatted bookmarks + * + * @return mixed[] Template data after active plugins render_picwall hook execution. + */ + protected function executeHooks(array $linksToDisplay): array + { + $data = [ + 'linksToDisplay' => $linksToDisplay, + ]; + $this->container->pluginManager->executeHooks( + 'render_picwall', + $data, + ['loggedin' => $this->container->loginManager->isLoggedIn()] + ); + + return $data; + } +} diff --git a/application/front/exceptions/ThumbnailsDisabledException.php b/application/front/exceptions/ThumbnailsDisabledException.php new file mode 100644 index 00000000..1b9cf5b7 --- /dev/null +++ b/application/front/exceptions/ThumbnailsDisabledException.php @@ -0,0 +1,15 @@ +doneUpdates; } + + /** + * With the Slim routing system, default header link should be `./` instead of `?`. + * Otherwise you can not go back to the home page. Example: `/picture-wall` -> `/picture-wall?` instead of `/`. + */ + public function updateMethodRelativeHomeLink(): bool + { + $link = trim($this->conf->get('general.header_link')); + if ($link[0] === '?') { + $link = './'. ltrim($link, '?'); + + $this->conf->set('general.header_link', $link, true, true); + } + + return true; + } } diff --git a/doc/md/RSS-feeds.md b/doc/md/RSS-feeds.md index d943218e..71f4d7ee 100644 --- a/doc/md/RSS-feeds.md +++ b/doc/md/RSS-feeds.md @@ -21,8 +21,8 @@ For example, if you want to subscribe only to links tagged `photography`: - Click on the `RSS Feed` button. - You are presented with an RSS feed showing only these links. Subscribe to it to receive only updates with this tag. - The same method **also works for a full-text search** (_Search_ box) **and for the Picture Wall** (want to only see pictures about `nature`?) -- You can also build the URLs manually: +- You can also build the URLs manually: - `https://my.shaarli.domain/?do=rss&searchtags=nature` - - `https://my.shaarli.domain/links/?do=picwall&searchterm=poney` + - `https://my.shaarli.domain/links/picture-wall?searchterm=poney` ![](images/rss-filter-1.png) ![](images/rss-filter-2.png) diff --git a/doc/md/Translations.md b/doc/md/Translations.md index 58b92da3..dfdd021e 100644 --- a/doc/md/Translations.md +++ b/doc/md/Translations.md @@ -42,7 +42,7 @@ http:///?post http:///?do=export http:///?do=import http:///login -http:///?do=picwall +http:///picture-wall http:///?do=pluginadmin http:///?do=tagcloud http:///?do=taglist diff --git a/index.php b/index.php index d3cb33d3..c639a3bc 100644 --- a/index.php +++ b/index.php @@ -610,37 +610,7 @@ function renderPage($conf, $pluginManager, $bookmarkService, $history, $sessionM // -------- Picture wall if ($targetPage == Router::$PAGE_PICWALL) { - $PAGE->assign('pagetitle', t('Picture wall') .' - '. $conf->get('general.title', 'Shaarli')); - if (! $conf->get('thumbnails.mode', Thumbnailer::MODE_NONE) === Thumbnailer::MODE_NONE) { - $PAGE->assign('linksToDisplay', []); - $PAGE->renderPage('picwall'); - exit; - } - - // Optionally filter the results: - $links = $bookmarkService->search($_GET); - $linksToDisplay = []; - - // Get only bookmarks which have a thumbnail. - // Note: we do not retrieve thumbnails here, the request is too heavy. - $factory = new FormatterFactory($conf, $loginManager->isLoggedIn()); - $formatter = $factory->getFormatter(); - foreach ($links as $key => $link) { - if ($link->getThumbnail() !== false) { - $linksToDisplay[] = $formatter->format($link); - } - } - - $data = [ - 'linksToDisplay' => $linksToDisplay, - ]; - $pluginManager->executeHooks('render_picwall', $data, ['loggedin' => $loginManager->isLoggedIn()]); - - foreach ($data as $key => $value) { - $PAGE->assign($key, $value); - } - - $PAGE->renderPage('picwall'); + header('Location: ./picture-wall'); exit; } @@ -1944,6 +1914,7 @@ $app->group('/api/v1', function () { $app->group('', function () { $this->get('/login', '\Shaarli\Front\Controller\LoginController:index')->setName('login'); + $this->get('/picture-wall', '\Shaarli\Front\Controller\PictureWallController:index')->setName('picwall'); })->add('\Shaarli\Front\ShaarliMiddleware'); $response = $app->run(true); diff --git a/tests/container/ContainerBuilderTest.php b/tests/container/ContainerBuilderTest.php index 9b97ed6d..cc2eb37f 100644 --- a/tests/container/ContainerBuilderTest.php +++ b/tests/container/ContainerBuilderTest.php @@ -7,6 +7,7 @@ namespace Shaarli\Container; use PHPUnit\Framework\TestCase; use Shaarli\Bookmark\BookmarkServiceInterface; use Shaarli\Config\ConfigManager; +use Shaarli\Formatter\FormatterFactory; use Shaarli\History; use Shaarli\Render\PageBuilder; use Shaarli\Security\LoginManager; @@ -30,7 +31,9 @@ class ContainerBuilderTest extends TestCase { $this->conf = new ConfigManager('tests/utils/config/configJson'); $this->sessionManager = $this->createMock(SessionManager::class); + $this->loginManager = $this->createMock(LoginManager::class); + $this->loginManager->method('isLoggedIn')->willReturn(true); $this->containerBuilder = new ContainerBuilder($this->conf, $this->sessionManager, $this->loginManager); } @@ -45,5 +48,6 @@ class ContainerBuilderTest extends TestCase static::assertInstanceOf(History::class, $container->history); static::assertInstanceOf(BookmarkServiceInterface::class, $container->bookmarkService); static::assertInstanceOf(PageBuilder::class, $container->pageBuilder); + static::assertInstanceOf(FormatterFactory::class, $container->formatterFactory); } } diff --git a/tests/front/controller/PictureWallControllerTest.php b/tests/front/controller/PictureWallControllerTest.php new file mode 100644 index 00000000..63802abd --- /dev/null +++ b/tests/front/controller/PictureWallControllerTest.php @@ -0,0 +1,180 @@ +container = $this->createMock(ShaarliContainer::class); + $this->controller = new PictureWallController($this->container); + } + + public function testValidControllerInvokeDefault(): void + { + $this->createValidContainerMockSet(); + + $request = $this->createMock(Request::class); + $request->expects(static::once())->method('getQueryParams')->willReturn([]); + $response = new Response(); + + // ConfigManager: thumbnails are enabled + $this->container->conf->method('get')->willReturnCallback(function (string $parameter, $default) { + if ($parameter === 'thumbnails.mode') { + return Thumbnailer::MODE_COMMON; + } + + return $default; + }); + + // Save RainTPL assigned variables + $assignedVariables = []; + $this->container->pageBuilder + ->expects(static::atLeastOnce()) + ->method('assign') + ->willReturnCallback(function ($key, $value) use (&$assignedVariables) { + $assignedVariables[$key] = $value; + + return $this; + }) + ; + + // Links dataset: 2 links with thumbnails + $this->container->bookmarkService + ->expects(static::once()) + ->method('search') + ->willReturnCallback(function (array $parameters, ?string $visibility): array { + // Visibility is set through the container, not the call + static::assertNull($visibility); + + // No query parameters + if (count($parameters) === 0) { + return [ + (new Bookmark())->setId(1)->setUrl('http://url.tld')->setThumbnail('thumb1'), + (new Bookmark())->setId(2)->setUrl('http://url2.tld'), + (new Bookmark())->setId(3)->setUrl('http://url3.tld')->setThumbnail('thumb2'), + ]; + } + }) + ; + + // Make sure that PluginManager hook is triggered + $this->container->pluginManager + ->expects(static::at(0)) + ->method('executeHooks') + ->willReturnCallback(function (string $hook, array $data, array $param): array { + static::assertSame('render_picwall', $hook); + static::assertArrayHasKey('linksToDisplay', $data); + static::assertCount(2, $data['linksToDisplay']); + static::assertSame(1, $data['linksToDisplay'][0]['id']); + static::assertSame(3, $data['linksToDisplay'][1]['id']); + static::assertArrayHasKey('loggedin', $param); + + return $data; + }); + + $result = $this->controller->index($request, $response); + + static::assertSame(200, $result->getStatusCode()); + static::assertSame('picwall', (string) $result->getBody()); + static::assertSame('Picture wall - Shaarli', $assignedVariables['pagetitle']); + static::assertCount(2, $assignedVariables['linksToDisplay']); + + $link = $assignedVariables['linksToDisplay'][0]; + + static::assertSame(1, $link['id']); + static::assertSame('http://url.tld', $link['url']); + static::assertSame('thumb1', $link['thumbnail']); + + $link = $assignedVariables['linksToDisplay'][1]; + + static::assertSame(3, $link['id']); + static::assertSame('http://url3.tld', $link['url']); + static::assertSame('thumb2', $link['thumbnail']); + } + + public function testControllerWithThumbnailsDisabled(): void + { + $this->expectException(ThumbnailsDisabledException::class); + + $this->createValidContainerMockSet(); + + $request = $this->createMock(Request::class); + $response = new Response(); + + // ConfigManager: thumbnails are disabled + $this->container->conf->method('get')->willReturnCallback(function (string $parameter, $default) { + if ($parameter === 'thumbnails.mode') { + return Thumbnailer::MODE_NONE; + } + + return $default; + }); + + $this->controller->index($request, $response); + } + + protected function createValidContainerMockSet(): void + { + $loginManager = $this->createMock(LoginManager::class); + $this->container->loginManager = $loginManager; + + // Config + $conf = $this->createMock(ConfigManager::class); + $this->container->conf = $conf; + + // PageBuilder + $pageBuilder = $this->createMock(PageBuilder::class); + $pageBuilder + ->method('render') + ->willReturnCallback(function (string $template): string { + return $template; + }) + ; + $this->container->pageBuilder = $pageBuilder; + + // Plugin Manager + $pluginManager = $this->createMock(PluginManager::class); + $this->container->pluginManager = $pluginManager; + + // BookmarkService + $bookmarkService = $this->createMock(BookmarkServiceInterface::class); + $this->container->bookmarkService = $bookmarkService; + + // Formatter + $formatterFactory = $this->createMock(FormatterFactory::class); + $formatterFactory + ->method('getFormatter') + ->willReturnCallback(function (string $type): BookmarkFormatter { + if ($type === 'raw') { + return new BookmarkRawFormatter($this->container->conf, true); + } + }) + ; + $this->container->formatterFactory = $formatterFactory; + } +} diff --git a/tpl/default/page.header.html b/tpl/default/page.header.html index 69e2fcd7..116265a5 100644 --- a/tpl/default/page.header.html +++ b/tpl/default/page.header.html @@ -34,7 +34,7 @@ {if="$thumbnails_enabled"}
  • - {'Picture wall'|t} + {'Picture wall'|t}
  • {/if}
  • diff --git a/tpl/default/picwall.html b/tpl/default/picwall.html index da5101db..5343abd6 100644 --- a/tpl/default/picwall.html +++ b/tpl/default/picwall.html @@ -5,58 +5,51 @@ {include="page.header"} -{if="!$thumbnails_enabled"} -
    -
    - {'Picture wall unavailable (thumbnails are disabled).'|t} -
    -
    -{else} - {if="count($linksToDisplay)===0 && $is_logged_in"} -
    -
    - {'There is no cached thumbnail. Try to synchronize them.'|t} -
    + +{if="count($linksToDisplay)===0 && $is_logged_in"} +
    +
    + {'There is no cached thumbnail. Try to synchronize them.'|t}
    - {/if} +
    +{/if} -
    -
    -
    - {$countPics=count($linksToDisplay)} -

    {'Picture Wall'|t} - {$countPics} {'pics'|t}

    +
    +
    +
    + {$countPics=count($linksToDisplay)} +

    {'Picture Wall'|t} - {$countPics} {'pics'|t}

    -
    - {loop="$plugin_start_zone"} - {$value} - {/loop} -
    +
    + {loop="$plugin_start_zone"} + {$value} + {/loop} +
    -
    - {loop="$linksToDisplay"} -
    - {ignore}RainTPL hack: put the 2 src on two different line to avoid path replace bug{/ignore} - - {$value.title} - {loop="$value.picwall_plugin"} - {$value} - {/loop} -
    - {/loop} -
    -
    +
    + {loop="$linksToDisplay"} +
    + {ignore}RainTPL hack: put the 2 src on two different line to avoid path replace bug{/ignore} + + {$value.title} + {loop="$value.picwall_plugin"} + {$value} + {/loop} +
    + {/loop} +
    +
    -
    - {loop="$plugin_end_zone"} - {$value} - {/loop} -
    +
    + {loop="$plugin_end_zone"} + {$value} + {/loop}
    -
    -{/if} +
    +
    {include="page.footer"} diff --git a/tpl/vintage/page.header.html b/tpl/vintage/page.header.html index f1869a04..ce938421 100644 --- a/tpl/vintage/page.header.html +++ b/tpl/vintage/page.header.html @@ -32,7 +32,7 @@
  • ATOM Feed
  • {/if}
  • Tag cloud
  • -
  • Picture wall
  • +
  • Picture wall
  • Daily
  • {loop="$plugins_header.buttons_toolbar"}