indent_size = 2
[*.php]
-max_line_length = 100
+max_line_length = 120
[Dockerfile]
max_line_length = 80
/**
* Get the Thumbnail.
*
- * @return string|bool
+ * @return string|bool|null
*/
public function getThumbnail()
{
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;
return new PluginManager($container->conf);
};
+ $container['formatterFactory'] = function (ShaarliContainer $container): FormatterFactory {
+ return new FormatterFactory($container->conf, $container->loginManager->isLoggedIn());
+ };
+
return $container;
}
}
use Shaarli\Bookmark\BookmarkServiceInterface;
use Shaarli\Config\ConfigManager;
+use Shaarli\Formatter\FormatterFactory;
use Shaarli\History;
use Shaarli\Plugin\PluginManager;
use Shaarli\Render\PageBuilder;
* @property BookmarkServiceInterface $bookmarkService
* @property PageBuilder $pageBuilder
* @property PluginManager $pluginManager
+ * @property FormatterFactory $formatterFactory
*/
class ShaarliContainer extends Container
{
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+namespace Shaarli\Front\Controller;
+
+use Shaarli\Front\Exception\ThumbnailsDisabledException;
+use Shaarli\Thumbnailer;
+use Slim\Http\Request;
+use Slim\Http\Response;
+
+/**
+ * Class PicturesWallController
+ *
+ * Slim controller used to render the pictures wall page.
+ * If thumbnails mode is set to NONE, we just render the template without any image.
+ *
+ * @package Front\Controller
+ */
+class PictureWallController extends ShaarliController
+{
+ public function index(Request $request, Response $response): Response
+ {
+ if ($this->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;
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+namespace Shaarli\Front\Exception;
+
+class ThumbnailsDisabledException extends ShaarliException
+{
+ public function __construct()
+ {
+ $message = t('Picture wall unavailable (thumbnails are disabled).');
+
+ parent::__construct($message, 400);
+ }
+}
namespace Shaarli\Updater;
-use Shaarli\Config\ConfigManager;
use Shaarli\Bookmark\BookmarkServiceInterface;
+use Shaarli\Config\ConfigManager;
use Shaarli\Updater\Exception\UpdaterException;
/**
{
return $this->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;
+ }
}
- 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)
http://<replace_domain>/?do=export
http://<replace_domain>/?do=import
http://<replace_domain>/login
-http://<replace_domain>/?do=picwall
+http://<replace_domain>/picture-wall
http://<replace_domain>/?do=pluginadmin
http://<replace_domain>/?do=tagcloud
http://<replace_domain>/?do=taglist
// -------- 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;
}
$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);
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;
{
$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);
}
static::assertInstanceOf(History::class, $container->history);
static::assertInstanceOf(BookmarkServiceInterface::class, $container->bookmarkService);
static::assertInstanceOf(PageBuilder::class, $container->pageBuilder);
+ static::assertInstanceOf(FormatterFactory::class, $container->formatterFactory);
}
}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+namespace Shaarli\Front\Controller;
+
+use PHPUnit\Framework\TestCase;
+use Shaarli\Bookmark\Bookmark;
+use Shaarli\Bookmark\BookmarkServiceInterface;
+use Shaarli\Config\ConfigManager;
+use Shaarli\Container\ShaarliContainer;
+use Shaarli\Formatter\BookmarkFormatter;
+use Shaarli\Formatter\BookmarkRawFormatter;
+use Shaarli\Formatter\FormatterFactory;
+use Shaarli\Front\Exception\ThumbnailsDisabledException;
+use Shaarli\Plugin\PluginManager;
+use Shaarli\Render\PageBuilder;
+use Shaarli\Security\LoginManager;
+use Shaarli\Thumbnailer;
+use Slim\Http\Request;
+use Slim\Http\Response;
+
+class PictureWallControllerTest extends TestCase
+{
+ /** @var ShaarliContainer */
+ protected $container;
+
+ /** @var PictureWallController */
+ protected $controller;
+
+ public function setUp(): void
+ {
+ $this->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;
+ }
+}
</li>
{if="$thumbnails_enabled"}
<li class="pure-menu-item" id="shaarli-menu-picwall">
- <a href="./?do=picwall{$searchcrits}" class="pure-menu-link">{'Picture wall'|t}</a>
+ <a href="./picture-wall?{function="ltrim($searchcrits, '&')"}" class="pure-menu-link">{'Picture wall'|t}</a>
</li>
{/if}
<li class="pure-menu-item" id="shaarli-menu-daily">
</head>
<body>
{include="page.header"}
-{if="!$thumbnails_enabled"}
-<div class="pure-g pure-alert pure-alert-warning page-single-alert">
- <div class="pure-u-1 center">
- {'Picture wall unavailable (thumbnails are disabled).'|t}
- </div>
-</div>
-{else}
- {if="count($linksToDisplay)===0 && $is_logged_in"}
- <div class="pure-g pure-alert pure-alert-warning page-single-alert">
- <div class="pure-u-1 center">
- {'There is no cached thumbnail. Try to <a href="./?do=thumbs_update">synchronize them</a>.'|t}
- </div>
+
+{if="count($linksToDisplay)===0 && $is_logged_in"}
+ <div class="pure-g pure-alert pure-alert-warning page-single-alert">
+ <div class="pure-u-1 center">
+ {'There is no cached thumbnail. Try to <a href="./?do=thumbs_update">synchronize them</a>.'|t}
</div>
- {/if}
+ </div>
+{/if}
- <div class="pure-g">
- <div class="pure-u-lg-1-6 pure-u-1-24"></div>
- <div class="pure-u-lg-2-3 pure-u-22-24 page-form page-visitor">
- {$countPics=count($linksToDisplay)}
- <h2 class="window-title">{'Picture Wall'|t} - {$countPics} {'pics'|t}</h2>
+<div class="pure-g">
+ <div class="pure-u-lg-1-6 pure-u-1-24"></div>
+ <div class="pure-u-lg-2-3 pure-u-22-24 page-form page-visitor">
+ {$countPics=count($linksToDisplay)}
+ <h2 class="window-title">{'Picture Wall'|t} - {$countPics} {'pics'|t}</h2>
- <div id="plugin_zone_start_picwall" class="plugin_zone">
- {loop="$plugin_start_zone"}
- {$value}
- {/loop}
- </div>
+ <div id="plugin_zone_start_picwall" class="plugin_zone">
+ {loop="$plugin_start_zone"}
+ {$value}
+ {/loop}
+ </div>
- <div id="picwall-container" class="picwall-container" role="list">
- {loop="$linksToDisplay"}
- <div class="picwall-pictureframe" role="listitem">
- {ignore}RainTPL hack: put the 2 src on two different line to avoid path replace bug{/ignore}
- <img data-src="{$value.thumbnail}#" class="b-lazy"
- src=""
- alt="" width="{$thumbnails_width}" height="{$thumbnails_height}" />
- <a href="{$value.real_url}"><span class="info">{$value.title}</span></a>
- {loop="$value.picwall_plugin"}
- {$value}
- {/loop}
- </div>
- {/loop}
- <div class="clear"></div>
- </div>
+ <div id="picwall-container" class="picwall-container" role="list">
+ {loop="$linksToDisplay"}
+ <div class="picwall-pictureframe" role="listitem">
+ {ignore}RainTPL hack: put the 2 src on two different line to avoid path replace bug{/ignore}
+ <img data-src="{$value.thumbnail}#" class="b-lazy"
+ src=""
+ alt="" width="{$thumbnails_width}" height="{$thumbnails_height}" />
+ <a href="{$value.real_url}"><span class="info">{$value.title}</span></a>
+ {loop="$value.picwall_plugin"}
+ {$value}
+ {/loop}
+ </div>
+ {/loop}
+ <div class="clear"></div>
+ </div>
- <div id="plugin_zone_end_picwall" class="plugin_zone">
- {loop="$plugin_end_zone"}
- {$value}
- {/loop}
- </div>
+ <div id="plugin_zone_end_picwall" class="plugin_zone">
+ {loop="$plugin_end_zone"}
+ {$value}
+ {/loop}
</div>
- <div class="pure-u-lg-1-6 pure-u-1-24"></div>
</div>
-{/if}
+ <div class="pure-u-lg-1-6 pure-u-1-24"></div>
+</div>
{include="page.footer"}
<script src="js/thumbnails.min.js?v={$version_hash}"></script>
<li><a href="{$feedurl}?do=atom{$searchcrits}" class="nomobile">ATOM Feed</a></li>
{/if}
<li><a href="./?do=tagcloud">Tag cloud</a></li>
- <li><a href="./?do=picwall{$searchcrits}">Picture wall</a></li>
+ <li><a href="./picture-wall{function="ltrim($searchcrits, '&')"}">Picture wall</a></li>
<li><a href="./?do=daily">Daily</a></li>
{loop="$plugins_header.buttons_toolbar"}
<li><a