From 2883c6d0a71db174ee8df7548178a8fbee486e25 Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Sun, 15 Nov 2020 12:05:08 +0100 Subject: [PATCH 01/33] Daily RSS - Remove relative description (today, yesterday) It is not useful for the RSS feed, as every new entry will be 'yesterday', and it requires an update the next day. --- .../controller/visitor/DailyController.php | 2 +- application/helper/DailyPageHelper.php | 18 ++++++++----- tests/helper/DailyPageHelperTest.php | 27 +++++++++++++++++++ 3 files changed, 39 insertions(+), 8 deletions(-) diff --git a/application/front/controller/visitor/DailyController.php b/application/front/controller/visitor/DailyController.php index 846cfe22..5ae89299 100644 --- a/application/front/controller/visitor/DailyController.php +++ b/application/front/controller/visitor/DailyController.php @@ -131,7 +131,7 @@ class DailyController extends ShaarliVisitorController $dataPerDay[$day] = [ 'date' => $endDateTime, 'date_rss' => $endDateTime->format(DateTime::RSS), - 'date_human' => DailyPageHelper::getDescriptionByType($type, $dayDateTime), + 'date_human' => DailyPageHelper::getDescriptionByType($type, $dayDateTime, false), 'absolute_url' => $indexUrl . 'daily?' . $type . '=' . $day, 'links' => [], ]; diff --git a/application/helper/DailyPageHelper.php b/application/helper/DailyPageHelper.php index 5fabc907..9bdb7ba5 100644 --- a/application/helper/DailyPageHelper.php +++ b/application/helper/DailyPageHelper.php @@ -154,16 +154,20 @@ class DailyPageHelper * Get localized description of the time period depending on given datetime and type. * Example: for a month period, it returns `October, 2020`. * - * @param string $type month/week/day - * @param \DateTimeImmutable $requested DateTime extracted from request input - * (should come from extractRequestedDateTime) + * @param string $type month/week/day + * @param \DateTimeImmutable $requested DateTime extracted from request input + * (should come from extractRequestedDateTime) + * @param bool $includeRelative Include relative date description (today, yesterday, etc.) * * @return string Localized time period description * * @throws \Exception Type not supported. */ - public static function getDescriptionByType(string $type, \DateTimeImmutable $requested): string - { + public static function getDescriptionByType( + string $type, + \DateTimeImmutable $requested, + bool $includeRelative = true + ): string { switch ($type) { case static::MONTH: return $requested->format('F') . ', ' . $requested->format('Y'); @@ -172,9 +176,9 @@ class DailyPageHelper return t('Week') . ' ' . $requested->format('W') . ' (' . format_date($requested, false) . ')'; case static::DAY: $out = ''; - if ($requested->format('Ymd') === date('Ymd')) { + if ($includeRelative && $requested->format('Ymd') === date('Ymd')) { $out = t('Today') . ' - '; - } elseif ($requested->format('Ymd') === date('Ymd', strtotime('-1 days'))) { + } elseif ($includeRelative && $requested->format('Ymd') === date('Ymd', strtotime('-1 days'))) { $out = t('Yesterday') . ' - '; } return $out . format_date($requested, false); diff --git a/tests/helper/DailyPageHelperTest.php b/tests/helper/DailyPageHelperTest.php index 5255b7b1..6238e648 100644 --- a/tests/helper/DailyPageHelperTest.php +++ b/tests/helper/DailyPageHelperTest.php @@ -121,6 +121,19 @@ class DailyPageHelperTest extends TestCase static::assertEquals($expectedDescription, $description); } + /** + * @dataProvider getDescriptionsByTypeNotIncludeRelative + */ + public function testGeDescriptionsByTypeNotIncludeRelative( + string $type, + \DateTimeImmutable $dateTime, + string $expectedDescription + ): void { + $description = DailyPageHelper::getDescriptionByType($type, $dateTime, false); + + static::assertEquals($expectedDescription, $description); + } + public function getDescriptionByTypeExceptionUnknownType(): void { $this->expectException(\Exception::class); @@ -248,6 +261,20 @@ class DailyPageHelperTest extends TestCase ]; } + /** + * Data provider for testGeDescriptionsByTypeNotIncludeRelative() test method. + */ + public function getDescriptionsByTypeNotIncludeRelative(): array + { + return [ + [DailyPageHelper::DAY, $date = new \DateTimeImmutable(), $date->format('F j, Y')], + [DailyPageHelper::DAY, $date = new \DateTimeImmutable('-1 day'), $date->format('F j, Y')], + [DailyPageHelper::DAY, new \DateTimeImmutable('2020-10-09 04:05:06'), 'October 9, 2020'], + [DailyPageHelper::WEEK, new \DateTimeImmutable('2020-10-09 04:05:06'), 'Week 41 (October 5, 2020)'], + [DailyPageHelper::MONTH, new \DateTimeImmutable('2020-10-09 04:05:06'), 'October, 2020'], + ]; + } + /** * Data provider for testGetDescriptionsByType() test method. */ From a6e9c08499f9f79dad88cb3ae9eacda0e0c34c96 Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Tue, 27 Oct 2020 19:23:45 +0100 Subject: [PATCH 02/33] Plugin system: allow plugins to provide custom routes - each route will be prefixed by `/plugin/` - add a new template for plugins rendering - add a live example in the demo_plugin Check out the "Plugin System" documentation for more detail. Related to #143 --- application/container/ContainerBuilder.php | 17 ++--- application/plugin/PluginManager.php | 64 +++++++++++++++++++ .../exception/PluginInvalidRouteException.php | 26 ++++++++ doc/md/dev/Plugin-system.md | 25 ++++++++ index.php | 22 ++++++- phpcs.xml | 1 + plugins/demo_plugin/DemoPluginController.php | 24 +++++++ plugins/demo_plugin/demo_plugin.php | 19 +++++- tests/PluginManagerTest.php | 39 +++++++++++ tests/container/ContainerBuilderTest.php | 5 ++ tests/plugins/test/test.php | 16 +++++ .../test_route_invalid/test_route_invalid.php | 12 ++++ tpl/default/pluginscontent.html | 13 ++++ 13 files changed, 270 insertions(+), 13 deletions(-) create mode 100644 application/plugin/exception/PluginInvalidRouteException.php create mode 100644 plugins/demo_plugin/DemoPluginController.php create mode 100644 tests/plugins/test_route_invalid/test_route_invalid.php create mode 100644 tpl/default/pluginscontent.html diff --git a/application/container/ContainerBuilder.php b/application/container/ContainerBuilder.php index f0234eca..6d69a880 100644 --- a/application/container/ContainerBuilder.php +++ b/application/container/ContainerBuilder.php @@ -50,6 +50,9 @@ class ContainerBuilder /** @var LoginManager */ protected $login; + /** @var PluginManager */ + protected $pluginManager; + /** @var LoggerInterface */ protected $logger; @@ -61,12 +64,14 @@ class ContainerBuilder SessionManager $session, CookieManager $cookieManager, LoginManager $login, + PluginManager $pluginManager, LoggerInterface $logger ) { $this->conf = $conf; $this->session = $session; $this->login = $login; $this->cookieManager = $cookieManager; + $this->pluginManager = $pluginManager; $this->logger = $logger; } @@ -78,12 +83,10 @@ class ContainerBuilder $container['sessionManager'] = $this->session; $container['cookieManager'] = $this->cookieManager; $container['loginManager'] = $this->login; + $container['pluginManager'] = $this->pluginManager; $container['logger'] = $this->logger; $container['basePath'] = $this->basePath; - $container['plugins'] = function (ShaarliContainer $container): PluginManager { - return new PluginManager($container->conf); - }; $container['history'] = function (ShaarliContainer $container): History { return new History($container->conf->get('resource.history')); @@ -113,14 +116,6 @@ class ContainerBuilder ); }; - $container['pluginManager'] = function (ShaarliContainer $container): PluginManager { - $pluginManager = new PluginManager($container->conf); - - $pluginManager->load($container->conf->get('general.enabled_plugins')); - - return $pluginManager; - }; - $container['formatterFactory'] = function (ShaarliContainer $container): FormatterFactory { return new FormatterFactory( $container->conf, diff --git a/application/plugin/PluginManager.php b/application/plugin/PluginManager.php index 3ea55728..7fc0cb04 100644 --- a/application/plugin/PluginManager.php +++ b/application/plugin/PluginManager.php @@ -4,6 +4,7 @@ namespace Shaarli\Plugin; use Shaarli\Config\ConfigManager; use Shaarli\Plugin\Exception\PluginFileNotFoundException; +use Shaarli\Plugin\Exception\PluginInvalidRouteException; /** * Class PluginManager @@ -26,6 +27,14 @@ class PluginManager */ private $loadedPlugins = []; + /** @var array List of registered routes. Contains keys: + * - `method`: HTTP method, GET/POST/PUT/PATCH/DELETE + * - `route` (path): without prefix, e.g. `/up/{variable}` + * It will be later prefixed by `/plugin//`. + * - `callable` string, function name or FQN class's method, e.g. `demo_plugin_custom_controller`. + */ + protected $registeredRoutes = []; + /** * @var ConfigManager Configuration Manager instance. */ @@ -86,6 +95,9 @@ class PluginManager $this->loadPlugin($dirs[$index], $plugin); } catch (PluginFileNotFoundException $e) { error_log($e->getMessage()); + } catch (\Throwable $e) { + $error = $plugin . t(' [plugin incompatibility]: ') . $e->getMessage(); + $this->errors = array_unique(array_merge($this->errors, [$error])); } } } @@ -166,6 +178,22 @@ class PluginManager } } + $registerRouteFunction = $pluginName . '_register_routes'; + $routes = null; + if (function_exists($registerRouteFunction)) { + $routes = call_user_func($registerRouteFunction); + } + + if ($routes !== null) { + foreach ($routes as $route) { + if (static::validateRouteRegistration($route)) { + $this->registeredRoutes[$pluginName][] = $route; + } else { + throw new PluginInvalidRouteException($pluginName); + } + } + } + $this->loadedPlugins[] = $pluginName; } @@ -237,6 +265,14 @@ class PluginManager return $metaData; } + /** + * @return array List of registered custom routes by plugins. + */ + public function getRegisteredRoutes(): array + { + return $this->registeredRoutes; + } + /** * Return the list of encountered errors. * @@ -246,4 +282,32 @@ class PluginManager { return $this->errors; } + + /** + * Checks whether provided input is valid to register a new route. + * It must contain keys `method`, `route`, `callable` (all strings). + * + * @param string[] $input + * + * @return bool + */ + protected static function validateRouteRegistration(array $input): bool + { + if ( + !array_key_exists('method', $input) + || !in_array(strtoupper($input['method']), ['GET', 'PUT', 'PATCH', 'POST', 'DELETE']) + ) { + return false; + } + + if (!array_key_exists('route', $input) || !preg_match('#^[a-z\d/\.\-_]+$#', $input['route'])) { + return false; + } + + if (!array_key_exists('callable', $input)) { + return false; + } + + return true; + } } diff --git a/application/plugin/exception/PluginInvalidRouteException.php b/application/plugin/exception/PluginInvalidRouteException.php new file mode 100644 index 00000000..6ba9bc43 --- /dev/null +++ b/application/plugin/exception/PluginInvalidRouteException.php @@ -0,0 +1,26 @@ +message = 'trying to register invalid route.'; + } +} diff --git a/doc/md/dev/Plugin-system.md b/doc/md/dev/Plugin-system.md index f09fadc2..79654011 100644 --- a/doc/md/dev/Plugin-system.md +++ b/doc/md/dev/Plugin-system.md @@ -139,6 +139,31 @@ Each file contain two keys: > Note: In PHP, `parse_ini_file()` seems to want strings to be between by quotes `"` in the ini file. +### Register plugin's routes + +Shaarli lets you register custom Slim routes for your plugin. + +To register a route, the plugin must include a function called `function _register_routes(): array`. + +This method must return an array of routes, each entry must contain the following keys: + + - `method`: HTTP method, `GET/POST/PUT/PATCH/DELETE` + - `route` (path): without prefix, e.g. `/up/{variable}` + It will be later prefixed by `/plugin//`. + - `callable` string, function name or FQN class's method to execute, e.g. `demo_plugin_custom_controller`. + +Callable functions or methods must have `Slim\Http\Request` and `Slim\Http\Response` parameters +and return a `Slim\Http\Response`. We recommend creating a dedicated class and extend either +`ShaarliVisitorController` or `ShaarliAdminController` to use helper functions they provide. + +A dedicated plugin template is available for rendering content: `pluginscontent.html` using `content` placeholder. + +> **Warning**: plugins are not able to use RainTPL template engine for their content due to technical restrictions. +> RainTPL does not allow to register multiple template folders, so all HTML rendering must be done within plugin +> custom controller. + +Check out the `demo_plugin` for a live example: `GET /plugin/demo_plugin/custom`. + ### Understanding relative paths Because Shaarli is a self-hosted tool, an instance can either be installed at the root directory, or under a subfolder. diff --git a/index.php b/index.php index 1eb7659a..862c53ef 100644 --- a/index.php +++ b/index.php @@ -31,6 +31,7 @@ use Psr\Log\LogLevel; use Shaarli\Config\ConfigManager; use Shaarli\Container\ContainerBuilder; use Shaarli\Languages; +use Shaarli\Plugin\PluginManager; use Shaarli\Security\BanManager; use Shaarli\Security\CookieManager; use Shaarli\Security\LoginManager; @@ -87,7 +88,17 @@ date_default_timezone_set($conf->get('general.timezone', 'UTC')); $loginManager->checkLoginState(client_ip_id($_SERVER)); -$containerBuilder = new ContainerBuilder($conf, $sessionManager, $cookieManager, $loginManager, $logger); +$pluginManager = new PluginManager($conf); +$pluginManager->load($conf->get('general.enabled_plugins', [])); + +$containerBuilder = new ContainerBuilder( + $conf, + $sessionManager, + $cookieManager, + $loginManager, + $pluginManager, + $logger +); $container = $containerBuilder->build(); $app = new App($container); @@ -154,6 +165,15 @@ $app->group('/admin', function () { $this->get('/visibility/{visibility}', '\Shaarli\Front\Controller\Admin\SessionFilterController:visibility'); })->add('\Shaarli\Front\ShaarliAdminMiddleware'); +$app->group('/plugin', function () use ($pluginManager) { + foreach ($pluginManager->getRegisteredRoutes() as $pluginName => $routes) { + $this->group('/' . $pluginName, function () use ($routes) { + foreach ($routes as $route) { + $this->{strtolower($route['method'])}('/' . ltrim($route['route'], '/'), $route['callable']); + } + }); + } +})->add('\Shaarli\Front\ShaarliMiddleware'); // REST API routes $app->group('/api/v1', function () { diff --git a/phpcs.xml b/phpcs.xml index c559e35d..9bdc8720 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -18,5 +18,6 @@ index.php + plugins/* diff --git a/plugins/demo_plugin/DemoPluginController.php b/plugins/demo_plugin/DemoPluginController.php new file mode 100644 index 00000000..b8ace9c8 --- /dev/null +++ b/plugins/demo_plugin/DemoPluginController.php @@ -0,0 +1,24 @@ +assignView( + 'content', + '
' . + 'This is a demo page. I have access to Shaarli container, so I\'m free to do whatever I want here.' . + '
' + ); + + return $response->write($this->render('pluginscontent')); + } +} diff --git a/plugins/demo_plugin/demo_plugin.php b/plugins/demo_plugin/demo_plugin.php index 22d27b68..15cfc2c5 100644 --- a/plugins/demo_plugin/demo_plugin.php +++ b/plugins/demo_plugin/demo_plugin.php @@ -7,6 +7,8 @@ * Can be used by plugin developers to make their own plugin. */ +require_once __DIR__ . '/DemoPluginController.php'; + /* * RENDER HEADER, INCLUDES, FOOTER * @@ -60,6 +62,17 @@ function demo_plugin_init($conf) return $errors; } +function demo_plugin_register_routes(): array +{ + return [ + [ + 'method' => 'GET', + 'route' => '/custom', + 'callable' => 'Shaarli\DemoPlugin\DemoPluginController:index', + ], + ]; +} + /** * Hook render_header. * Executed on every page render. @@ -304,7 +317,11 @@ function hook_demo_plugin_render_editlink($data) function hook_demo_plugin_render_tools($data) { // field_plugin - $data['tools_plugin'][] = 'tools_plugin'; + $data['tools_plugin'][] = ''; return $data; } diff --git a/tests/PluginManagerTest.php b/tests/PluginManagerTest.php index efef5e87..8947f679 100644 --- a/tests/PluginManagerTest.php +++ b/tests/PluginManagerTest.php @@ -120,4 +120,43 @@ class PluginManagerTest extends \Shaarli\TestCase $this->assertEquals('test plugin', $meta[self::$pluginName]['description']); $this->assertEquals($expectedParameters, $meta[self::$pluginName]['parameters']); } + + /** + * Test plugin custom routes - note that there is no check on callable functions + */ + public function testRegisteredRoutes(): void + { + PluginManager::$PLUGINS_PATH = self::$pluginPath; + $this->pluginManager->load([self::$pluginName]); + + $expectedParameters = [ + [ + 'method' => 'GET', + 'route' => '/test', + 'callable' => 'getFunction', + ], + [ + 'method' => 'POST', + 'route' => '/custom', + 'callable' => 'postFunction', + ], + ]; + $meta = $this->pluginManager->getRegisteredRoutes(); + static::assertSame($expectedParameters, $meta[self::$pluginName]); + } + + /** + * Test plugin custom routes with invalid route + */ + public function testRegisteredRoutesInvalid(): void + { + $plugin = 'test_route_invalid'; + $this->pluginManager->load([$plugin]); + + $meta = $this->pluginManager->getRegisteredRoutes(); + static::assertSame([], $meta); + + $errors = $this->pluginManager->getErrors(); + static::assertSame(['test_route_invalid [plugin incompatibility]: trying to register invalid route.'], $errors); + } } diff --git a/tests/container/ContainerBuilderTest.php b/tests/container/ContainerBuilderTest.php index 3d43c344..04d4ef01 100644 --- a/tests/container/ContainerBuilderTest.php +++ b/tests/container/ContainerBuilderTest.php @@ -43,11 +43,15 @@ class ContainerBuilderTest extends TestCase /** @var CookieManager */ protected $cookieManager; + /** @var PluginManager */ + protected $pluginManager; + public function setUp(): void { $this->conf = new ConfigManager('tests/utils/config/configJson'); $this->sessionManager = $this->createMock(SessionManager::class); $this->cookieManager = $this->createMock(CookieManager::class); + $this->pluginManager = $this->createMock(PluginManager::class); $this->loginManager = $this->createMock(LoginManager::class); $this->loginManager->method('isLoggedIn')->willReturn(true); @@ -57,6 +61,7 @@ class ContainerBuilderTest extends TestCase $this->sessionManager, $this->cookieManager, $this->loginManager, + $this->pluginManager, $this->createMock(LoggerInterface::class) ); } diff --git a/tests/plugins/test/test.php b/tests/plugins/test/test.php index 03be4f4e..34cd339e 100644 --- a/tests/plugins/test/test.php +++ b/tests/plugins/test/test.php @@ -27,3 +27,19 @@ function hook_test_error() { new Unknown(); } + +function test_register_routes(): array +{ + return [ + [ + 'method' => 'GET', + 'route' => '/test', + 'callable' => 'getFunction', + ], + [ + 'method' => 'POST', + 'route' => '/custom', + 'callable' => 'postFunction', + ], + ]; +} diff --git a/tests/plugins/test_route_invalid/test_route_invalid.php b/tests/plugins/test_route_invalid/test_route_invalid.php new file mode 100644 index 00000000..0c5a5101 --- /dev/null +++ b/tests/plugins/test_route_invalid/test_route_invalid.php @@ -0,0 +1,12 @@ + 'GET', + 'route' => 'not a route', + 'callable' => 'getFunction', + ], + ]; +} diff --git a/tpl/default/pluginscontent.html b/tpl/default/pluginscontent.html new file mode 100644 index 00000000..1e4f6b80 --- /dev/null +++ b/tpl/default/pluginscontent.html @@ -0,0 +1,13 @@ + + + + {include="includes"} + + + {include="page.header"} + + {$content} + + {include="page.footer"} + + From 70507b86037450450a5ac74597304f2ee1cb4c0d Mon Sep 17 00:00:00 2001 From: nodiscc Date: Sun, 22 Nov 2020 11:06:14 +0000 Subject: [PATCH 03/33] ConfigureControllerTest.php: update expected languages number to 6 Following the addition of russian translations in #1642 Fixes https://github.com/shaarli/Shaarli/issues/1647 --- tests/front/controller/admin/ConfigureControllerTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/front/controller/admin/ConfigureControllerTest.php b/tests/front/controller/admin/ConfigureControllerTest.php index d82db0a7..13644df9 100644 --- a/tests/front/controller/admin/ConfigureControllerTest.php +++ b/tests/front/controller/admin/ConfigureControllerTest.php @@ -62,7 +62,7 @@ class ConfigureControllerTest extends TestCase static::assertSame('privacy.hide_public_links', $assignedVariables['hide_public_links']); static::assertSame('api.enabled', $assignedVariables['api_enabled']); static::assertSame('api.secret', $assignedVariables['api_secret']); - static::assertCount(5, $assignedVariables['languages']); + static::assertCount(6, $assignedVariables['languages']); static::assertArrayHasKey('gd_enabled', $assignedVariables); static::assertSame('thumbnails.mode', $assignedVariables['thumbnails_mode']); } From 05c616f7a08936108d6feee4460dca9407e83d9f Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Tue, 24 Nov 2020 13:35:37 +0100 Subject: [PATCH 04/33] chmod -x russian translation file --- inc/languages/ru/LC_MESSAGES/shaarli.po | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 inc/languages/ru/LC_MESSAGES/shaarli.po diff --git a/inc/languages/ru/LC_MESSAGES/shaarli.po b/inc/languages/ru/LC_MESSAGES/shaarli.po old mode 100755 new mode 100644 From 8a6b7e96b7176e03238bbb1bcaa4c8b0c25e6358 Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Tue, 24 Nov 2020 13:28:17 +0100 Subject: [PATCH 05/33] Fix: soft fail if the mutex is not working And display the error in server admin page Fixes #1650 --- application/bookmark/BookmarkIO.php | 22 ++- .../controller/admin/ServerController.php | 7 +- .../controller/visitor/InstallController.php | 7 +- application/helper/ApplicationUtils.php | 16 ++ inc/languages/fr/LC_MESSAGES/shaarli.po | 186 +++++++++--------- 5 files changed, 145 insertions(+), 93 deletions(-) diff --git a/application/bookmark/BookmarkIO.php b/application/bookmark/BookmarkIO.php index c78dbe41..8439d470 100644 --- a/application/bookmark/BookmarkIO.php +++ b/application/bookmark/BookmarkIO.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Shaarli\Bookmark; +use malkusch\lock\exception\LockAcquireException; use malkusch\lock\mutex\Mutex; use malkusch\lock\mutex\NoMutex; use Shaarli\Bookmark\Exception\DatastoreNotInitializedException; @@ -80,7 +81,7 @@ class BookmarkIO } $content = null; - $this->mutex->synchronized(function () use (&$content) { + $this->synchronized(function () use (&$content) { $content = file_get_contents($this->datastore); }); @@ -119,11 +120,28 @@ class BookmarkIO $data = self::$phpPrefix . base64_encode(gzdeflate(serialize($links))) . self::$phpSuffix; - $this->mutex->synchronized(function () use ($data) { + $this->synchronized(function () use ($data) { file_put_contents( $this->datastore, $data ); }); } + + /** + * Wrapper applying mutex to provided function. + * If the lock can't be acquired (e.g. some shared hosting provider), we execute the function without mutex. + * + * @see https://github.com/shaarli/Shaarli/issues/1650 + * + * @param callable $function + */ + protected function synchronized(callable $function): void + { + try { + $this->mutex->synchronized($function); + } catch (LockAcquireException $exception) { + $function(); + } + } } diff --git a/application/front/controller/admin/ServerController.php b/application/front/controller/admin/ServerController.php index fabeaf2f..4b74f4a9 100644 --- a/application/front/controller/admin/ServerController.php +++ b/application/front/controller/admin/ServerController.php @@ -39,11 +39,16 @@ class ServerController extends ShaarliAdminController $currentVersion = $currentVersion === 'dev' ? $currentVersion : 'v' . $currentVersion; $phpEol = new \DateTimeImmutable(ApplicationUtils::getPhpEol(PHP_VERSION)); + $permissions = array_merge( + ApplicationUtils::checkResourcePermissions($this->container->conf), + ApplicationUtils::checkDatastoreMutex() + ); + $this->assignView('php_version', PHP_VERSION); $this->assignView('php_eol', format_date($phpEol, false)); $this->assignView('php_has_reached_eol', $phpEol < new \DateTimeImmutable()); $this->assignView('php_extensions', ApplicationUtils::getPhpExtensionsRequirement()); - $this->assignView('permissions', ApplicationUtils::checkResourcePermissions($this->container->conf)); + $this->assignView('permissions', $permissions); $this->assignView('release_url', $releaseUrl); $this->assignView('latest_version', $latestVersion); $this->assignView('current_version', $currentVersion); diff --git a/application/front/controller/visitor/InstallController.php b/application/front/controller/visitor/InstallController.php index bf965929..418d4a49 100644 --- a/application/front/controller/visitor/InstallController.php +++ b/application/front/controller/visitor/InstallController.php @@ -56,11 +56,16 @@ class InstallController extends ShaarliVisitorController $phpEol = new \DateTimeImmutable(ApplicationUtils::getPhpEol(PHP_VERSION)); + $permissions = array_merge( + ApplicationUtils::checkResourcePermissions($this->container->conf), + ApplicationUtils::checkDatastoreMutex() + ); + $this->assignView('php_version', PHP_VERSION); $this->assignView('php_eol', format_date($phpEol, false)); $this->assignView('php_has_reached_eol', $phpEol < new \DateTimeImmutable()); $this->assignView('php_extensions', ApplicationUtils::getPhpExtensionsRequirement()); - $this->assignView('permissions', ApplicationUtils::checkResourcePermissions($this->container->conf)); + $this->assignView('permissions', $permissions); $this->assignView('pagetitle', t('Install Shaarli')); diff --git a/application/helper/ApplicationUtils.php b/application/helper/ApplicationUtils.php index 212dd8e2..a6c03aae 100644 --- a/application/helper/ApplicationUtils.php +++ b/application/helper/ApplicationUtils.php @@ -3,6 +3,8 @@ namespace Shaarli\Helper; use Exception; +use malkusch\lock\exception\LockAcquireException; +use malkusch\lock\mutex\FlockMutex; use Shaarli\Config\ConfigManager; /** @@ -252,6 +254,20 @@ class ApplicationUtils return $errors; } + public static function checkDatastoreMutex(): array + { + $mutex = new FlockMutex(fopen(SHAARLI_MUTEX_FILE, 'r'), 2); + try { + $mutex->synchronized(function () { + return true; + }); + } catch (LockAcquireException $e) { + $errors[] = t('Lock can not be acquired on the datastore. You might encounter concurrent access issues.'); + } + + return $errors ?? []; + } + /** * Returns a salted hash representing the current Shaarli version. * diff --git a/inc/languages/fr/LC_MESSAGES/shaarli.po b/inc/languages/fr/LC_MESSAGES/shaarli.po index 26dede4e..01492af4 100644 --- a/inc/languages/fr/LC_MESSAGES/shaarli.po +++ b/inc/languages/fr/LC_MESSAGES/shaarli.po @@ -1,8 +1,8 @@ msgid "" msgstr "" "Project-Id-Version: Shaarli\n" -"POT-Creation-Date: 2020-11-09 14:39+0100\n" -"PO-Revision-Date: 2020-11-09 14:42+0100\n" +"POT-Creation-Date: 2020-11-24 13:13+0100\n" +"PO-Revision-Date: 2020-11-24 13:14+0100\n" "Last-Translator: \n" "Language-Team: Shaarli\n" "Language: fr_FR\n" @@ -20,31 +20,31 @@ msgstr "" "X-Poedit-SearchPath-3: init.php\n" "X-Poedit-SearchPath-4: plugins\n" -#: application/History.php:180 +#: application/History.php:181 msgid "History file isn't readable or writable" msgstr "Le fichier d'historique n'est pas accessible en lecture ou en écriture" -#: application/History.php:191 +#: application/History.php:192 msgid "Could not parse history file" msgstr "Format incorrect pour le fichier d'historique" -#: application/Languages.php:181 +#: application/Languages.php:184 msgid "Automatic" msgstr "Automatique" -#: application/Languages.php:182 +#: application/Languages.php:185 msgid "German" msgstr "Allemand" -#: application/Languages.php:183 +#: application/Languages.php:186 msgid "English" msgstr "Anglais" -#: application/Languages.php:184 +#: application/Languages.php:187 msgid "French" msgstr "Français" -#: application/Languages.php:185 +#: application/Languages.php:188 msgid "Japanese" msgstr "Japonais" @@ -56,46 +56,46 @@ msgstr "" "l'extension php-gd doit être chargée pour utiliser les miniatures. Les " "miniatures sont désormais désactivées. Rechargez la page." -#: application/Utils.php:402 +#: application/Utils.php:405 msgid "Setting not set" msgstr "Paramètre non défini" -#: application/Utils.php:409 +#: application/Utils.php:412 msgid "Unlimited" msgstr "Illimité" -#: application/Utils.php:412 +#: application/Utils.php:415 msgid "B" msgstr "o" -#: application/Utils.php:412 +#: application/Utils.php:415 msgid "kiB" msgstr "ko" -#: application/Utils.php:412 +#: application/Utils.php:415 msgid "MiB" msgstr "Mo" -#: application/Utils.php:412 +#: application/Utils.php:415 msgid "GiB" msgstr "Go" -#: application/bookmark/BookmarkFileService.php:183 -#: application/bookmark/BookmarkFileService.php:205 -#: application/bookmark/BookmarkFileService.php:227 -#: application/bookmark/BookmarkFileService.php:241 +#: application/bookmark/BookmarkFileService.php:185 +#: application/bookmark/BookmarkFileService.php:207 +#: application/bookmark/BookmarkFileService.php:229 +#: application/bookmark/BookmarkFileService.php:243 msgid "You're not authorized to alter the datastore" msgstr "Vous n'êtes pas autorisé à modifier les données" -#: application/bookmark/BookmarkFileService.php:208 +#: application/bookmark/BookmarkFileService.php:210 msgid "This bookmarks already exists" msgstr "Ce marque-page existe déjà" -#: application/bookmark/BookmarkInitializer.php:39 +#: application/bookmark/BookmarkInitializer.php:42 msgid "(private bookmark with thumbnail demo)" msgstr "(marque page privé avec une miniature)" -#: application/bookmark/BookmarkInitializer.php:42 +#: application/bookmark/BookmarkInitializer.php:45 msgid "" "Shaarli will automatically pick up the thumbnail for links to a variety of " "websites.\n" @@ -118,11 +118,11 @@ msgstr "" "\n" "Maintenant, vous pouvez modifier ou supprimer les shaares créés par défaut.\n" -#: application/bookmark/BookmarkInitializer.php:55 +#: application/bookmark/BookmarkInitializer.php:58 msgid "Note: Shaare descriptions" msgstr "Note : Description des Shaares" -#: application/bookmark/BookmarkInitializer.php:57 +#: application/bookmark/BookmarkInitializer.php:60 msgid "" "Adding a shaare without entering a URL creates a text-only \"note\" post " "such as this one.\n" @@ -186,7 +186,7 @@ msgstr "" "| Citron | Fruit | Jaune | 30 |\n" "| Carotte | Légume | Orange | 14 |\n" -#: application/bookmark/BookmarkInitializer.php:91 +#: application/bookmark/BookmarkInitializer.php:94 #: application/legacy/LegacyLinkDB.php:246 #: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:15 #: tmp/page.footer.b91ef64efc3688266305ea9b42e5017e.rtpl.php:48 @@ -198,7 +198,7 @@ msgstr "" "Le gestionnaire de marque-pages personnel, minimaliste, et sans base de " "données" -#: application/bookmark/BookmarkInitializer.php:94 +#: application/bookmark/BookmarkInitializer.php:97 msgid "" "Welcome to Shaarli!\n" "\n" @@ -247,11 +247,11 @@ msgstr "" "issues) si vous avez une suggestion ou si vous rencontrez un problème.\n" " \n" -#: application/bookmark/exception/BookmarkNotFoundException.php:13 +#: application/bookmark/exception/BookmarkNotFoundException.php:14 msgid "The link you are trying to reach does not exist or has been deleted." msgstr "Le lien que vous essayez de consulter n'existe pas ou a été supprimé." -#: application/config/ConfigJson.php:52 application/config/ConfigPhp.php:129 +#: application/config/ConfigJson.php:52 application/config/ConfigPhp.php:131 msgid "" "Shaarli could not create the config file. Please make sure Shaarli has the " "right to write in the folder is it installed in." @@ -259,12 +259,12 @@ msgstr "" "Shaarli n'a pas pu créer le fichier de configuration. Merci de vérifier que " "Shaarli a les droits d'écriture dans le dossier dans lequel il est installé." -#: application/config/ConfigManager.php:136 -#: application/config/ConfigManager.php:163 +#: application/config/ConfigManager.php:137 +#: application/config/ConfigManager.php:164 msgid "Invalid setting key parameter. String expected, got: " msgstr "Clé de paramétrage invalide. Chaîne de caractères obtenue, attendu : " -#: application/config/exception/MissingFieldConfigException.php:21 +#: application/config/exception/MissingFieldConfigException.php:20 #, php-format msgid "Configuration value is required for %s" msgstr "Le paramètre %s est obligatoire" @@ -274,48 +274,48 @@ msgid "An error occurred while trying to save plugins loading order." msgstr "" "Une erreur s'est produite lors de la sauvegarde de l'ordre des extensions." -#: application/config/exception/UnauthorizedConfigException.php:16 +#: application/config/exception/UnauthorizedConfigException.php:15 msgid "You are not authorized to alter config." msgstr "Vous n'êtes pas autorisé à modifier la configuration." -#: application/exceptions/IOException.php:22 +#: application/exceptions/IOException.php:23 msgid "Error accessing" msgstr "Une erreur s'est produite en accédant à" -#: application/feed/FeedBuilder.php:179 +#: application/feed/FeedBuilder.php:180 msgid "Direct link" msgstr "Liens directs" -#: application/feed/FeedBuilder.php:181 +#: application/feed/FeedBuilder.php:182 #: tmp/daily.b91ef64efc3688266305ea9b42e5017e.rtpl.php:103 #: tmp/dailyrss.b91ef64efc3688266305ea9b42e5017e.rtpl.php:26 #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:179 msgid "Permalink" msgstr "Permalien" -#: application/front/controller/admin/ConfigureController.php:54 +#: application/front/controller/admin/ConfigureController.php:56 #: tmp/configure.b91ef64efc3688266305ea9b42e5017e.rtpl.php:24 msgid "Configure" msgstr "Configurer" -#: application/front/controller/admin/ConfigureController.php:102 -#: application/legacy/LegacyUpdater.php:537 +#: application/front/controller/admin/ConfigureController.php:106 +#: application/legacy/LegacyUpdater.php:539 msgid "You have enabled or changed thumbnails mode." msgstr "Vous avez activé ou changé le mode de miniatures." -#: application/front/controller/admin/ConfigureController.php:103 -#: application/front/controller/admin/ServerController.php:75 -#: application/legacy/LegacyUpdater.php:538 +#: application/front/controller/admin/ConfigureController.php:108 +#: application/front/controller/admin/ServerController.php:76 +#: application/legacy/LegacyUpdater.php:540 msgid "Please synchronize them." msgstr "Merci de les synchroniser." -#: application/front/controller/admin/ConfigureController.php:113 -#: application/front/controller/visitor/InstallController.php:146 +#: application/front/controller/admin/ConfigureController.php:119 +#: application/front/controller/visitor/InstallController.php:149 msgid "Error while writing config file after configuration update." msgstr "" "Une erreur s'est produite lors de la sauvegarde du fichier de configuration." -#: application/front/controller/admin/ConfigureController.php:122 +#: application/front/controller/admin/ConfigureController.php:128 msgid "Configuration was saved." msgstr "La configuration a été sauvegardée." @@ -433,7 +433,7 @@ msgstr "Administration serveur" msgid "Thumbnails cache has been cleared." msgstr "Le cache des miniatures a été vidé." -#: application/front/controller/admin/ServerController.php:83 +#: application/front/controller/admin/ServerController.php:85 msgid "Shaarli's cache folder has been cleared!" msgstr "Le dossier de cache de Shaarli a été vidé !" @@ -459,18 +459,18 @@ msgstr "Le lien avec l'identifiant %s n'a pas pu être trouvé." msgid "Invalid visibility provided." msgstr "Visibilité du lien non valide." -#: application/front/controller/admin/ShaarePublishController.php:171 +#: application/front/controller/admin/ShaarePublishController.php:173 #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:171 msgid "Edit" msgstr "Modifier" -#: application/front/controller/admin/ShaarePublishController.php:174 +#: application/front/controller/admin/ShaarePublishController.php:176 #: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28 #: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:28 msgid "Shaare" msgstr "Shaare" -#: application/front/controller/admin/ShaarePublishController.php:205 +#: application/front/controller/admin/ShaarePublishController.php:208 msgid "Note: " msgstr "Note : " @@ -485,7 +485,7 @@ msgstr "Mise à jour des miniatures" msgid "Tools" msgstr "Outils" -#: application/front/controller/visitor/BookmarkListController.php:120 +#: application/front/controller/visitor/BookmarkListController.php:121 msgid "Search: " msgstr "Recherche : " @@ -535,12 +535,12 @@ msgstr "Une erreur inattendue s'est produite." msgid "Requested page could not be found." msgstr "La page demandée n'a pas pu être trouvée." -#: application/front/controller/visitor/InstallController.php:64 +#: application/front/controller/visitor/InstallController.php:65 #: tmp/install.b91ef64efc3688266305ea9b42e5017e.rtpl.php:22 msgid "Install Shaarli" msgstr "Installation de Shaarli" -#: application/front/controller/visitor/InstallController.php:83 +#: application/front/controller/visitor/InstallController.php:85 #, php-format msgid "" "
Sessions do not seem to work correctly on your server.
Make sure the " @@ -559,14 +559,14 @@ msgstr "" "des cookies. Nous vous recommandons d'accéder à votre serveur depuis son " "adresse IP ou un Fully Qualified Domain Name.
" -#: application/front/controller/visitor/InstallController.php:154 +#: application/front/controller/visitor/InstallController.php:157 msgid "" "Shaarli is now configured. Please login and start shaaring your bookmarks!" msgstr "" "Shaarli est maintenant configuré. Vous pouvez vous connecter et commencez à " "shaare vos liens !" -#: application/front/controller/visitor/InstallController.php:168 +#: application/front/controller/visitor/InstallController.php:171 msgid "Insufficient permissions:" msgstr "Permissions insuffisantes :" @@ -580,7 +580,7 @@ msgstr "Permissions insuffisantes :" msgid "Login" msgstr "Connexion" -#: application/front/controller/visitor/LoginController.php:77 +#: application/front/controller/visitor/LoginController.php:78 msgid "Wrong login/password." msgstr "Nom d'utilisateur ou mot de passe incorrect(s)." @@ -620,7 +620,7 @@ msgstr "" msgid "Wrong token." msgstr "Jeton invalide." -#: application/helper/ApplicationUtils.php:162 +#: application/helper/ApplicationUtils.php:165 #, php-format msgid "" "Your PHP version is obsolete! Shaarli requires at least PHP %s, and thus " @@ -631,52 +631,60 @@ msgstr "" "peut donc pas fonctionner. Votre version de PHP a des failles de sécurités " "connues et devrait être mise à jour au plus tôt." -#: application/helper/ApplicationUtils.php:195 -#: application/helper/ApplicationUtils.php:215 +#: application/helper/ApplicationUtils.php:200 +#: application/helper/ApplicationUtils.php:220 msgid "directory is not readable" msgstr "le répertoire n'est pas accessible en lecture" -#: application/helper/ApplicationUtils.php:218 +#: application/helper/ApplicationUtils.php:223 msgid "directory is not writable" msgstr "le répertoire n'est pas accessible en écriture" -#: application/helper/ApplicationUtils.php:240 +#: application/helper/ApplicationUtils.php:247 msgid "file is not readable" msgstr "le fichier n'est pas accessible en lecture" -#: application/helper/ApplicationUtils.php:243 +#: application/helper/ApplicationUtils.php:250 msgid "file is not writable" msgstr "le fichier n'est pas accessible en écriture" -#: application/helper/ApplicationUtils.php:277 +#: application/helper/ApplicationUtils.php:260 +msgid "" +"Lock can not be acquired on the datastore. You might encounter concurrent " +"access issues." +msgstr "" +"Le fichier datastore ne peut pas être verrouillé. Vous pourriez rencontrer " +"des problèmes d'accès concurrents." + +#: application/helper/ApplicationUtils.php:293 msgid "Configuration parsing" msgstr "Chargement de la configuration" -#: application/helper/ApplicationUtils.php:278 +#: application/helper/ApplicationUtils.php:294 msgid "Slim Framework (routing, etc.)" msgstr "Slim Framwork (routage, etc.)" -#: application/helper/ApplicationUtils.php:279 +#: application/helper/ApplicationUtils.php:295 msgid "Multibyte (Unicode) string support" msgstr "Support des chaînes de caractère multibytes (Unicode)" -#: application/helper/ApplicationUtils.php:280 +#: application/helper/ApplicationUtils.php:296 msgid "Required to use thumbnails" msgstr "Obligatoire pour utiliser les miniatures" -#: application/helper/ApplicationUtils.php:281 +#: application/helper/ApplicationUtils.php:297 msgid "Localized text sorting (e.g. e->è->f)" msgstr "Tri des textes traduits (ex : e->è->f)" -#: application/helper/ApplicationUtils.php:282 +#: application/helper/ApplicationUtils.php:298 msgid "Better retrieval of bookmark metadata and thumbnail" msgstr "Meilleure récupération des meta-données des marque-pages et minatures" -#: application/helper/ApplicationUtils.php:283 +#: application/helper/ApplicationUtils.php:299 msgid "Use the translation system in gettext mode" msgstr "Utiliser le système de traduction en mode gettext" -#: application/helper/ApplicationUtils.php:284 +#: application/helper/ApplicationUtils.php:300 msgid "Login using LDAP server" msgstr "Authentification via un serveur LDAP" @@ -750,7 +758,7 @@ msgstr "" msgid "Couldn't retrieve updater class methods." msgstr "Impossible de récupérer les méthodes de la classe Updater." -#: application/legacy/LegacyUpdater.php:538 +#: application/legacy/LegacyUpdater.php:540 msgid "" msgstr "" @@ -776,11 +784,11 @@ msgstr "" "a été importé avec succès en %d secondes : %d liens importés, %d liens " "écrasés, %d liens ignorés." -#: application/plugin/PluginManager.php:124 +#: application/plugin/PluginManager.php:125 msgid " [plugin incompatibility]: " msgstr " [incompatibilité de l'extension] : " -#: application/plugin/exception/PluginFileNotFoundException.php:21 +#: application/plugin/exception/PluginFileNotFoundException.php:22 #, php-format msgid "Plugin \"%s\" files not found." msgstr "Les fichiers de l'extension \"%s\" sont introuvables." @@ -794,7 +802,7 @@ msgstr "Impossible de purger %s : le répertoire n'existe pas" msgid "An error occurred while running the update " msgstr "Une erreur s'est produite lors de l'exécution de la mise à jour " -#: index.php:80 +#: index.php:81 msgid "Shared bookmarks on " msgstr "Liens partagés sur " @@ -811,11 +819,11 @@ msgstr "Shaare" msgid "Adds the addlink input on the linklist page." msgstr "Ajoute le formulaire d'ajout de liens sur la page principale." -#: plugins/archiveorg/archiveorg.php:28 +#: plugins/archiveorg/archiveorg.php:29 msgid "View on archive.org" msgstr "Voir sur archive.org" -#: plugins/archiveorg/archiveorg.php:41 +#: plugins/archiveorg/archiveorg.php:42 msgid "For each link, add an Archive.org icon." msgstr "Pour chaque lien, ajoute une icône pour Archive.org." @@ -845,7 +853,7 @@ msgstr "Couleur de fond (gris léger)" msgid "Dark main color (e.g. visited links)" msgstr "Couleur principale sombre (ex : les liens visités)" -#: plugins/demo_plugin/demo_plugin.php:477 +#: plugins/demo_plugin/demo_plugin.php:478 msgid "" "A demo plugin covering all use cases for template designers and plugin " "developers." @@ -853,11 +861,11 @@ msgstr "" "Une extension de démonstration couvrant tous les cas d'utilisation pour les " "designers de thèmes et les développeurs d'extensions." -#: plugins/demo_plugin/demo_plugin.php:478 +#: plugins/demo_plugin/demo_plugin.php:479 msgid "This is a parameter dedicated to the demo plugin. It'll be suffixed." msgstr "Ceci est un paramètre dédié au plugin de démo. Il sera suffixé." -#: plugins/demo_plugin/demo_plugin.php:479 +#: plugins/demo_plugin/demo_plugin.php:480 msgid "Other demo parameter" msgstr "Un autre paramètre de démo" @@ -879,7 +887,7 @@ msgstr "" msgid "Isso server URL (without 'http://')" msgstr "URL du serveur Isso (sans 'http://')" -#: plugins/piwik/piwik.php:23 +#: plugins/piwik/piwik.php:24 msgid "" "Piwik plugin error: Please define PIWIK_URL and PIWIK_SITEID in the plugin " "administration page." @@ -887,27 +895,27 @@ msgstr "" "Erreur de l'extension Piwik : Merci de définir les paramètres PIWIK_URL et " "PIWIK_SITEID dans la page d'administration des extensions." -#: plugins/piwik/piwik.php:72 +#: plugins/piwik/piwik.php:73 msgid "A plugin that adds Piwik tracking code to Shaarli pages." msgstr "Ajoute le code de traçage de Piwik sur les pages de Shaarli." -#: plugins/piwik/piwik.php:73 +#: plugins/piwik/piwik.php:74 msgid "Piwik URL" msgstr "URL de Piwik" -#: plugins/piwik/piwik.php:74 +#: plugins/piwik/piwik.php:75 msgid "Piwik site ID" msgstr "Site ID de Piwik" -#: plugins/playvideos/playvideos.php:25 +#: plugins/playvideos/playvideos.php:26 msgid "Video player" msgstr "Lecteur vidéo" -#: plugins/playvideos/playvideos.php:28 +#: plugins/playvideos/playvideos.php:29 msgid "Play Videos" msgstr "Jouer les vidéos" -#: plugins/playvideos/playvideos.php:59 +#: plugins/playvideos/playvideos.php:60 msgid "Add a button in the toolbar allowing to watch all videos." msgstr "" "Ajoute un bouton dans la barre de menu pour regarder toutes les vidéos." @@ -935,11 +943,11 @@ msgstr "Mauvaise réponse du hub %s" msgid "Enable PubSubHubbub feed publishing." msgstr "Active la publication de flux vers PubSubHubbub." -#: plugins/qrcode/qrcode.php:73 plugins/wallabag/wallabag.php:71 +#: plugins/qrcode/qrcode.php:74 plugins/wallabag/wallabag.php:72 msgid "For each link, add a QRCode icon." msgstr "Pour chaque lien, ajouter une icône de QRCode." -#: plugins/wallabag/wallabag.php:21 +#: plugins/wallabag/wallabag.php:22 msgid "" "Wallabag plugin error: Please define the \"WALLABAG_URL\" setting in the " "plugin administration page." @@ -947,15 +955,15 @@ msgstr "" "Erreur de l'extension Wallabag : Merci de définir le paramètre « " "WALLABAG_URL » dans la page d'administration des extensions." -#: plugins/wallabag/wallabag.php:48 +#: plugins/wallabag/wallabag.php:49 msgid "Save to wallabag" msgstr "Sauvegarder dans Wallabag" -#: plugins/wallabag/wallabag.php:72 +#: plugins/wallabag/wallabag.php:73 msgid "Wallabag API URL" msgstr "URL de l'API Wallabag" -#: plugins/wallabag/wallabag.php:73 +#: plugins/wallabag/wallabag.php:74 msgid "Wallabag API version (1 or 2)" msgstr "Version de l'API Wallabag (1 ou 2)" From 495545f2f07f00ead911908d8bee9a572889eced Mon Sep 17 00:00:00 2001 From: Doug Breaux <25640850+dougbreaux@users.noreply.github.com> Date: Fri, 4 Dec 2020 16:12:39 -0600 Subject: [PATCH 06/33] newer alpine (for newer PHP) and apk upgrade #1655 --- Dockerfile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index f6120b71..bedc9496 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,7 +26,7 @@ RUN cd shaarli \ # Stage 4: # - Shaarli image -FROM alpine:3.8 +FROM alpine:3.12 LABEL maintainer="Shaarli Community" RUN apk --update --no-cache add \ @@ -46,7 +46,8 @@ RUN apk --update --no-cache add \ php7-xml \ php7-simplexml \ php7-zlib \ - s6 + s6 \ + && apk -U upgrade COPY .docker/nginx.conf /etc/nginx/nginx.conf COPY .docker/php-fpm.conf /etc/php7/php-fpm.conf From ee07df357bce7bd407ba93fa6b10677b355f631f Mon Sep 17 00:00:00 2001 From: Emilien Klein Date: Sun, 6 Dec 2020 21:02:39 +0100 Subject: [PATCH 07/33] Upgrade alpine from 3.8 to 3.10 in armhf Dockerfile The Docker for armhf doesn't build anymore on Alpine 3.8, upgrading to 3.10. Building works fine on a Rapsberry Pi 4 running Raspbian GNU/Linux 10 (buster) This is the error with 3.8: ``` [2/4] Fetching packages... error css-loader@4.3.0: The engine "node" is incompatible with this module. Expected version ">= 10.13.0". error Found incompatible module info Visit https://yarnpkg.com/en/docs/cli/install for documentation about this command. The command '/bin/sh -c apk --update --no-cache add yarn nodejs-current python2 build-base && cd /shaarli && yarn install && yarn run build && rm -rf node_modules' returned a non-zero code: 1 ``` Not upgrading to 3.11, due to this error: ``` 2/4] Fetching packages... error browserslist@4.14.3: The engine "node" is incompatible with this module. Expected version "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7". Got "13.1.0" error Found incompatible module. info Visit https://yarnpkg.com/en/docs/cli/install for documentation about this command. The command '/bin/sh -c apk --update --no-cache add yarn nodejs-current python2 build-base && cd /shaarli && yarn install && yarn run build && rm -rf node_modules' returned a non-zero code: 1 ``` Not upgrading to 3.12 either, due to this error: ``` ERROR: unsatisfiable constraints: py2-pip (missing): required by: world[py2-pip] The command '/bin/sh -c apk --update --no-cache add py2-pip && cd /usr/src/app/shaarli && pip install --no-cache-dir mkdocs && mkdocs build --clean' returned a non-zero code: 1 ``` There's probably a way to have Python2 pip installed on 3.12, but I suppose other issues would arise (such as the one happening with 3.11), so only proposing to upgrade to 3.10 now. This would probably be looked at more in detail when merging the amd64 and arm/v7 Docker builds, see #1496. --- Dockerfile.armhf | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Dockerfile.armhf b/Dockerfile.armhf index 5bbf6680..471f2397 100644 --- a/Dockerfile.armhf +++ b/Dockerfile.armhf @@ -1,7 +1,7 @@ # Stage 1: # - Copy Shaarli sources # - Build documentation -FROM arm32v6/alpine:3.8 as docs +FROM arm32v6/alpine:3.10 as docs ADD . /usr/src/app/shaarli RUN apk --update --no-cache add py2-pip \ && cd /usr/src/app/shaarli \ @@ -10,7 +10,7 @@ RUN apk --update --no-cache add py2-pip \ # Stage 2: # - Resolve PHP dependencies with Composer -FROM arm32v6/alpine:3.8 as composer +FROM arm32v6/alpine:3.10 as composer COPY --from=docs /usr/src/app/shaarli /app/shaarli RUN apk --update --no-cache add php7-curl php7-mbstring php7-simplexml composer \ && cd /app/shaarli \ @@ -18,7 +18,7 @@ RUN apk --update --no-cache add php7-curl php7-mbstring php7-simplexml composer # Stage 3: # - Frontend dependencies -FROM arm32v6/alpine:3.8 as node +FROM arm32v6/alpine:3.10 as node COPY --from=composer /app/shaarli /shaarli RUN apk --update --no-cache add yarn nodejs-current python2 build-base \ && cd /shaarli \ @@ -28,7 +28,7 @@ RUN apk --update --no-cache add yarn nodejs-current python2 build-base \ # Stage 4: # - Shaarli image -FROM arm32v6/alpine:3.8 +FROM arm32v6/alpine:3.10 LABEL maintainer="Shaarli Community" RUN apk --update --no-cache add \ From 2a20e67f760fb0587c8ea5c8219ec4546dde55bd Mon Sep 17 00:00:00 2001 From: Doug Breaux <25640850+dougbreaux@users.noreply.github.com> Date: Sun, 6 Dec 2020 22:02:34 -0600 Subject: [PATCH 08/33] remove apk upgrade #1655 --- Dockerfile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index bedc9496..79d33130 100644 --- a/Dockerfile +++ b/Dockerfile @@ -46,8 +46,7 @@ RUN apk --update --no-cache add \ php7-xml \ php7-simplexml \ php7-zlib \ - s6 \ - && apk -U upgrade + s6 COPY .docker/nginx.conf /etc/nginx/nginx.conf COPY .docker/php-fpm.conf /etc/php7/php-fpm.conf From 5b74c67461dbfb3fe87486646b64afd14aec4860 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 11 Dec 2020 03:39:18 +0000 Subject: [PATCH 09/33] Bump ini from 1.3.5 to 1.3.7 Bumps [ini](https://github.com/isaacs/ini) from 1.3.5 to 1.3.7. - [Release notes](https://github.com/isaacs/ini/releases) - [Commits](https://github.com/isaacs/ini/compare/v1.3.5...v1.3.7) Signed-off-by: dependabot[bot] --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 55bd9827..97fb0fad 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3052,9 +3052,9 @@ inherits@2.0.3: integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= ini@^1.3.4, ini@^1.3.5: - version "1.3.5" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" - integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== + version "1.3.7" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.7.tgz#a09363e1911972ea16d7a8851005d84cf09a9a84" + integrity sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ== interpret@^1.4.0: version "1.4.0" From 6a3a78d023aa320138bb88505b58347db268264a Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Wed, 16 Dec 2020 14:04:32 +0100 Subject: [PATCH 10/33] Fix: synchronous metadata retrieval is failing in strict mode Metadata can now only be string or null. Fixes #1653 --- .../front/controller/admin/ShaarePublishController.php | 2 +- application/http/MetadataRetriever.php | 9 +++++++-- tests/http/MetadataRetrieverTest.php | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/application/front/controller/admin/ShaarePublishController.php b/application/front/controller/admin/ShaarePublishController.php index 4cbfcdc5..fb9cacc2 100644 --- a/application/front/controller/admin/ShaarePublishController.php +++ b/application/front/controller/admin/ShaarePublishController.php @@ -227,7 +227,7 @@ class ShaarePublishController extends ShaarliAdminController protected function buildFormData(array $link, bool $isNew, Request $request): array { - $link['tags'] = strlen($link['tags']) > 0 + $link['tags'] = $link['tags'] !== null && strlen($link['tags']) > 0 ? $link['tags'] . $this->container->conf->get('general.tags_separator', ' ') : $link['tags'] ; diff --git a/application/http/MetadataRetriever.php b/application/http/MetadataRetriever.php index 2e1401ec..cfc72583 100644 --- a/application/http/MetadataRetriever.php +++ b/application/http/MetadataRetriever.php @@ -60,10 +60,15 @@ class MetadataRetriever $title = mb_convert_encoding($title, 'utf-8', $charset); } - return [ + return array_map([$this, 'cleanMetadata'], [ 'title' => $title, 'description' => $description, 'tags' => $tags, - ]; + ]); + } + + protected function cleanMetadata($data): ?string + { + return !is_string($data) || empty(trim($data)) ? null : trim($data); } } diff --git a/tests/http/MetadataRetrieverTest.php b/tests/http/MetadataRetrieverTest.php index 3c9eaa0e..cae65091 100644 --- a/tests/http/MetadataRetrieverTest.php +++ b/tests/http/MetadataRetrieverTest.php @@ -41,7 +41,7 @@ class MetadataRetrieverTest extends TestCase $remoteCharset = 'utf-8'; $expectedResult = [ - 'title' => $remoteTitle, + 'title' => trim($remoteTitle), 'description' => $remoteDesc, 'tags' => $remoteTags, ]; From 88a8e284b2825cbd77c75dbbbf3655a961d9eb09 Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Thu, 17 Dec 2020 13:56:24 +0100 Subject: [PATCH 11/33] Fix metadata extract regex (2) Reference: https://stackoverflow.com/questions/8055727/negating-a-backreference-in-regular-expressions Fixes #1656 --- application/bookmark/LinkUtils.php | 6 ++++-- tests/bookmark/LinkUtilsTest.php | 10 ++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/application/bookmark/LinkUtils.php b/application/bookmark/LinkUtils.php index d65e97ed..0ab2d213 100644 --- a/application/bookmark/LinkUtils.php +++ b/application/bookmark/LinkUtils.php @@ -68,11 +68,13 @@ function html_extract_tag($tag, $html) $properties = implode('|', $propertiesKey); // We need a OR here to accept either 'property=og:noquote' or 'property="og:unrelated og:my-tag"' $orCondition = '["\']?(?:og:)?' . $tag . '["\']?|["\'][^\'"]*?(?:og:)?' . $tag . '[^\'"]*?[\'"]'; + // Support quotes in double quoted content, and the other way around + $content = 'content=(["\'])((?:(?!\1).)*)\1'; // Try to retrieve OpenGraph tag. - $ogRegex = '#]+(?:' . $properties . ')=(?:' . $orCondition . ')[^>]*content=(["\'])([^\1]*?)\1.*?>#'; + $ogRegex = '#]+(?:' . $properties . ')=(?:' . $orCondition . ')[^>]*' . $content . '.*?>#'; // If the attributes are not in the order property => content (e.g. Github) // New regex to keep this readable... more or less. - $ogRegexReverse = '#]+content=(["\'])([^\1]*?)\1[^>]+(?:' . $properties . ')=(?:' . $orCondition . ').*?>#'; + $ogRegexReverse = '#]+' . $content . '[^>]+(?:' . $properties . ')=(?:' . $orCondition . ').*?>#'; if ( preg_match($ogRegex, $html, $matches) > 0 diff --git a/tests/bookmark/LinkUtilsTest.php b/tests/bookmark/LinkUtilsTest.php index ddab4e3c..46a7f1fe 100644 --- a/tests/bookmark/LinkUtilsTest.php +++ b/tests/bookmark/LinkUtilsTest.php @@ -245,6 +245,16 @@ class LinkUtilsTest extends TestCase $this->assertFalse(html_extract_tag('description', $html)); } + public function testHtmlExtractDescriptionFromGoogleRealCase(): void + { + $html = 'id="gsr">'. + ''. + 'assertSame('Bonnes fêtes de fin d\'année ! #GoogleDoodle', html_extract_tag('description', $html)); + } + /** * Test the header callback with valid value */ From f00600a283617286c813dc902fe3a2d66938b5fc Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Thu, 17 Dec 2020 15:43:33 +0100 Subject: [PATCH 12/33] Daily RSS Cache: invalidate cache base on the date Currently the cache is only invalidated when the datastore changes, while it should rely on selected period of time. Fixes #1659 --- application/feed/CachedPage.php | 45 ++++++--- .../controller/visitor/DailyController.php | 5 +- application/helper/DailyPageHelper.php | 66 ++++++++----- application/render/PageCacheManager.php | 14 ++- tests/feed/CachedPageTest.php | 57 +++++++++-- tests/helper/DailyPageHelperTest.php | 94 ++++++++++++++----- 6 files changed, 213 insertions(+), 68 deletions(-) diff --git a/application/feed/CachedPage.php b/application/feed/CachedPage.php index d809bdd9..c23c200f 100644 --- a/application/feed/CachedPage.php +++ b/application/feed/CachedPage.php @@ -1,34 +1,43 @@ cacheDir = $cacheDir; $this->filename = $this->cacheDir . '/' . sha1($url) . '.cache'; $this->shouldBeCached = $shouldBeCached; + $this->validityPeriod = $validityPeriod; } /** @@ -41,10 +50,20 @@ class CachedPage if (!$this->shouldBeCached) { return null; } - if (is_file($this->filename)) { - return file_get_contents($this->filename); + if (!is_file($this->filename)) { + return null; } - return null; + if ($this->validityPeriod !== null) { + $cacheDate = \DateTime::createFromFormat('U', (string) filemtime($this->filename)); + if ( + $cacheDate < $this->validityPeriod->getStartDate() + || $cacheDate > $this->validityPeriod->getEndDate() + ) { + return null; + } + } + + return file_get_contents($this->filename); } /** diff --git a/application/front/controller/visitor/DailyController.php b/application/front/controller/visitor/DailyController.php index 5ae89299..29492a5f 100644 --- a/application/front/controller/visitor/DailyController.php +++ b/application/front/controller/visitor/DailyController.php @@ -86,9 +86,11 @@ class DailyController extends ShaarliVisitorController public function rss(Request $request, Response $response): Response { $response = $response->withHeader('Content-Type', 'application/rss+xml; charset=utf-8'); + $type = DailyPageHelper::extractRequestedType($request); + $cacheDuration = DailyPageHelper::getCacheDatePeriodByType($type); $pageUrl = page_url($this->container->environment); - $cache = $this->container->pageCacheManager->getCachePage($pageUrl); + $cache = $this->container->pageCacheManager->getCachePage($pageUrl, $cacheDuration); $cached = $cache->cachedVersion(); if (!empty($cached)) { @@ -96,7 +98,6 @@ class DailyController extends ShaarliVisitorController } $days = []; - $type = DailyPageHelper::extractRequestedType($request); $format = DailyPageHelper::getFormatByType($type); $length = DailyPageHelper::getRssLengthByType($type); foreach ($this->container->bookmarkService->search() as $bookmark) { diff --git a/application/helper/DailyPageHelper.php b/application/helper/DailyPageHelper.php index 9bdb7ba5..05f95812 100644 --- a/application/helper/DailyPageHelper.php +++ b/application/helper/DailyPageHelper.php @@ -4,6 +4,9 @@ declare(strict_types=1); namespace Shaarli\Helper; +use DatePeriod; +use DateTimeImmutable; +use Exception; use Shaarli\Bookmark\Bookmark; use Slim\Http\Request; @@ -40,31 +43,31 @@ class DailyPageHelper * @param string|null $requestedDate Input string extracted from the request * @param Bookmark|null $latestBookmark Latest bookmark found in the datastore (by date) * - * @return \DateTimeImmutable from input or latest bookmark. + * @return DateTimeImmutable from input or latest bookmark. * - * @throws \Exception Type not supported. + * @throws Exception Type not supported. */ public static function extractRequestedDateTime( string $type, ?string $requestedDate, Bookmark $latestBookmark = null - ): \DateTimeImmutable { + ): DateTimeImmutable { $format = static::getFormatByType($type); if (empty($requestedDate)) { return $latestBookmark instanceof Bookmark - ? new \DateTimeImmutable($latestBookmark->getCreated()->format(\DateTime::ATOM)) - : new \DateTimeImmutable() + ? new DateTimeImmutable($latestBookmark->getCreated()->format(\DateTime::ATOM)) + : new DateTimeImmutable() ; } // W is not supported by createFromFormat... if ($type === static::WEEK) { - return (new \DateTimeImmutable()) + return (new DateTimeImmutable()) ->setISODate((int) substr($requestedDate, 0, 4), (int) substr($requestedDate, 4, 2)) ; } - return \DateTimeImmutable::createFromFormat($format, $requestedDate); + return DateTimeImmutable::createFromFormat($format, $requestedDate); } /** @@ -80,7 +83,7 @@ class DailyPageHelper * * @see https://www.php.net/manual/en/datetime.format.php * - * @throws \Exception Type not supported. + * @throws Exception Type not supported. */ public static function getFormatByType(string $type): string { @@ -92,7 +95,7 @@ class DailyPageHelper case static::DAY: return 'Ymd'; default: - throw new \Exception('Unsupported daily format type'); + throw new Exception('Unsupported daily format type'); } } @@ -102,14 +105,14 @@ class DailyPageHelper * and we don't want to alter original datetime. * * @param string $type month/week/day - * @param \DateTimeImmutable $requested DateTime extracted from request input + * @param DateTimeImmutable $requested DateTime extracted from request input * (should come from extractRequestedDateTime) * * @return \DateTimeInterface First DateTime of the time period * - * @throws \Exception Type not supported. + * @throws Exception Type not supported. */ - public static function getStartDateTimeByType(string $type, \DateTimeImmutable $requested): \DateTimeInterface + public static function getStartDateTimeByType(string $type, DateTimeImmutable $requested): \DateTimeInterface { switch ($type) { case static::MONTH: @@ -119,7 +122,7 @@ class DailyPageHelper case static::DAY: return $requested->modify('Today midnight'); default: - throw new \Exception('Unsupported daily format type'); + throw new Exception('Unsupported daily format type'); } } @@ -129,14 +132,14 @@ class DailyPageHelper * and we don't want to alter original datetime. * * @param string $type month/week/day - * @param \DateTimeImmutable $requested DateTime extracted from request input + * @param DateTimeImmutable $requested DateTime extracted from request input * (should come from extractRequestedDateTime) * * @return \DateTimeInterface Last DateTime of the time period * - * @throws \Exception Type not supported. + * @throws Exception Type not supported. */ - public static function getEndDateTimeByType(string $type, \DateTimeImmutable $requested): \DateTimeInterface + public static function getEndDateTimeByType(string $type, DateTimeImmutable $requested): \DateTimeInterface { switch ($type) { case static::MONTH: @@ -146,7 +149,7 @@ class DailyPageHelper case static::DAY: return $requested->modify('Today 23:59:59'); default: - throw new \Exception('Unsupported daily format type'); + throw new Exception('Unsupported daily format type'); } } @@ -161,7 +164,7 @@ class DailyPageHelper * * @return string Localized time period description * - * @throws \Exception Type not supported. + * @throws Exception Type not supported. */ public static function getDescriptionByType( string $type, @@ -183,7 +186,7 @@ class DailyPageHelper } return $out . format_date($requested, false); default: - throw new \Exception('Unsupported daily format type'); + throw new Exception('Unsupported daily format type'); } } @@ -194,7 +197,7 @@ class DailyPageHelper * * @return int number of elements * - * @throws \Exception Type not supported. + * @throws Exception Type not supported. */ public static function getRssLengthByType(string $type): int { @@ -206,7 +209,28 @@ class DailyPageHelper case static::DAY: return 30; // ~1 month default: - throw new \Exception('Unsupported daily format type'); + throw new Exception('Unsupported daily format type'); } } + + /** + * Get the number of items to display in the RSS feed depending on the given type. + * + * @param string $type month/week/day + * @param ?DateTimeImmutable $requested Currently only used for UT + * + * @return DatePeriod number of elements + * + * @throws Exception Type not supported. + */ + public static function getCacheDatePeriodByType(string $type, DateTimeImmutable $requested = null): DatePeriod + { + $requested = $requested ?? new DateTimeImmutable(); + + return new DatePeriod( + static::getStartDateTimeByType($type, $requested), + new \DateInterval('P1D'), + static::getEndDateTimeByType($type, $requested) + ); + } } diff --git a/application/render/PageCacheManager.php b/application/render/PageCacheManager.php index 97805c35..fe74bf27 100644 --- a/application/render/PageCacheManager.php +++ b/application/render/PageCacheManager.php @@ -2,6 +2,7 @@ namespace Shaarli\Render; +use DatePeriod; use Shaarli\Feed\CachedPage; /** @@ -49,12 +50,21 @@ class PageCacheManager $this->purgeCachedPages(); } - public function getCachePage(string $pageUrl): CachedPage + /** + * Get CachedPage instance for provided URL. + * + * @param string $pageUrl + * @param ?DatePeriod $validityPeriod Optionally specify a time limit on requested cache + * + * @return CachedPage + */ + public function getCachePage(string $pageUrl, DatePeriod $validityPeriod = null): CachedPage { return new CachedPage( $this->pageCacheDir, $pageUrl, - false === $this->isLoggedIn + false === $this->isLoggedIn, + $validityPeriod ); } } diff --git a/tests/feed/CachedPageTest.php b/tests/feed/CachedPageTest.php index 904db9dc..1decfaf3 100644 --- a/tests/feed/CachedPageTest.php +++ b/tests/feed/CachedPageTest.php @@ -40,10 +40,10 @@ class CachedPageTest extends \Shaarli\TestCase */ public function testConstruct() { - new CachedPage(self::$testCacheDir, '', true); - new CachedPage(self::$testCacheDir, '', false); - new CachedPage(self::$testCacheDir, 'http://shaar.li/feed/rss', true); - new CachedPage(self::$testCacheDir, 'http://shaar.li/feed/atom', false); + new CachedPage(self::$testCacheDir, '', true, null); + new CachedPage(self::$testCacheDir, '', false, null); + new CachedPage(self::$testCacheDir, 'http://shaar.li/feed/rss', true, null); + new CachedPage(self::$testCacheDir, 'http://shaar.li/feed/atom', false, null); $this->addToAssertionCount(1); } @@ -52,7 +52,7 @@ class CachedPageTest extends \Shaarli\TestCase */ public function testCache() { - $page = new CachedPage(self::$testCacheDir, self::$url, true); + $page = new CachedPage(self::$testCacheDir, self::$url, true, null); $this->assertFileNotExists(self::$filename); $page->cache('

Some content

'); @@ -68,7 +68,7 @@ class CachedPageTest extends \Shaarli\TestCase */ public function testShouldNotCache() { - $page = new CachedPage(self::$testCacheDir, self::$url, false); + $page = new CachedPage(self::$testCacheDir, self::$url, false, null); $this->assertFileNotExists(self::$filename); $page->cache('

Some content

'); @@ -80,7 +80,7 @@ class CachedPageTest extends \Shaarli\TestCase */ public function testCachedVersion() { - $page = new CachedPage(self::$testCacheDir, self::$url, true); + $page = new CachedPage(self::$testCacheDir, self::$url, true, null); $this->assertFileNotExists(self::$filename); $page->cache('

Some content

'); @@ -96,7 +96,7 @@ class CachedPageTest extends \Shaarli\TestCase */ public function testCachedVersionNoFile() { - $page = new CachedPage(self::$testCacheDir, self::$url, true); + $page = new CachedPage(self::$testCacheDir, self::$url, true, null); $this->assertFileNotExists(self::$filename); $this->assertEquals( @@ -110,7 +110,7 @@ class CachedPageTest extends \Shaarli\TestCase */ public function testNoCachedVersion() { - $page = new CachedPage(self::$testCacheDir, self::$url, false); + $page = new CachedPage(self::$testCacheDir, self::$url, false, null); $this->assertFileNotExists(self::$filename); $this->assertEquals( @@ -118,4 +118,43 @@ class CachedPageTest extends \Shaarli\TestCase $page->cachedVersion() ); } + + /** + * Return a page's cached content within date period + */ + public function testCachedVersionInDatePeriod() + { + $period = new \DatePeriod( + new \DateTime('yesterday'), + new \DateInterval('P1D'), + new \DateTime('tomorrow') + ); + $page = new CachedPage(self::$testCacheDir, self::$url, true, $period); + + $this->assertFileNotExists(self::$filename); + $page->cache('

Some content

'); + $this->assertFileExists(self::$filename); + $this->assertEquals( + '

Some content

', + $page->cachedVersion() + ); + } + + /** + * Return a page's cached content outside of date period + */ + public function testCachedVersionNotInDatePeriod() + { + $period = new \DatePeriod( + new \DateTime('yesterday noon'), + new \DateInterval('P1D'), + new \DateTime('yesterday midnight') + ); + $page = new CachedPage(self::$testCacheDir, self::$url, true, $period); + + $this->assertFileNotExists(self::$filename); + $page->cache('

Some content

'); + $this->assertFileExists(self::$filename); + $this->assertNull($page->cachedVersion()); + } } diff --git a/tests/helper/DailyPageHelperTest.php b/tests/helper/DailyPageHelperTest.php index 6238e648..2d745800 100644 --- a/tests/helper/DailyPageHelperTest.php +++ b/tests/helper/DailyPageHelperTest.php @@ -4,6 +4,8 @@ declare(strict_types=1); namespace Shaarli\Helper; +use DateTimeImmutable; +use DateTimeInterface; use Shaarli\Bookmark\Bookmark; use Shaarli\TestCase; use Slim\Http\Request; @@ -32,7 +34,7 @@ class DailyPageHelperTest extends TestCase string $type, string $input, ?Bookmark $bookmark, - \DateTimeInterface $expectedDateTime, + DateTimeInterface $expectedDateTime, string $compareFormat = 'Ymd' ): void { $dateTime = DailyPageHelper::extractRequestedDateTime($type, $input, $bookmark); @@ -71,8 +73,8 @@ class DailyPageHelperTest extends TestCase */ public function testGetStartDatesByType( string $type, - \DateTimeImmutable $dateTime, - \DateTimeInterface $expectedDateTime + DateTimeImmutable $dateTime, + DateTimeInterface $expectedDateTime ): void { $startDateTime = DailyPageHelper::getStartDateTimeByType($type, $dateTime); @@ -84,7 +86,7 @@ class DailyPageHelperTest extends TestCase $this->expectException(\Exception::class); $this->expectExceptionMessage('Unsupported daily format type'); - DailyPageHelper::getStartDateTimeByType('nope', new \DateTimeImmutable()); + DailyPageHelper::getStartDateTimeByType('nope', new DateTimeImmutable()); } /** @@ -92,8 +94,8 @@ class DailyPageHelperTest extends TestCase */ public function testGetEndDatesByType( string $type, - \DateTimeImmutable $dateTime, - \DateTimeInterface $expectedDateTime + DateTimeImmutable $dateTime, + DateTimeInterface $expectedDateTime ): void { $endDateTime = DailyPageHelper::getEndDateTimeByType($type, $dateTime); @@ -105,7 +107,7 @@ class DailyPageHelperTest extends TestCase $this->expectException(\Exception::class); $this->expectExceptionMessage('Unsupported daily format type'); - DailyPageHelper::getEndDateTimeByType('nope', new \DateTimeImmutable()); + DailyPageHelper::getEndDateTimeByType('nope', new DateTimeImmutable()); } /** @@ -113,7 +115,7 @@ class DailyPageHelperTest extends TestCase */ public function testGeDescriptionsByType( string $type, - \DateTimeImmutable $dateTime, + DateTimeImmutable $dateTime, string $expectedDescription ): void { $description = DailyPageHelper::getDescriptionByType($type, $dateTime); @@ -139,7 +141,7 @@ class DailyPageHelperTest extends TestCase $this->expectException(\Exception::class); $this->expectExceptionMessage('Unsupported daily format type'); - DailyPageHelper::getDescriptionByType('nope', new \DateTimeImmutable()); + DailyPageHelper::getDescriptionByType('nope', new DateTimeImmutable()); } /** @@ -159,6 +161,29 @@ class DailyPageHelperTest extends TestCase DailyPageHelper::getRssLengthByType('nope'); } + /** + * @dataProvider getCacheDatePeriodByType + */ + public function testGetCacheDatePeriodByType( + string $type, + DateTimeImmutable $requested, + DateTimeInterface $start, + DateTimeInterface $end + ): void { + $period = DailyPageHelper::getCacheDatePeriodByType($type, $requested); + + static::assertEquals($start, $period->getStartDate()); + static::assertEquals($end, $period->getEndDate()); + } + + public function testGetCacheDatePeriodByTypeExceptionUnknownType(): void + { + $this->expectException(\Exception::class); + $this->expectExceptionMessage('Unsupported daily format type'); + + DailyPageHelper::getCacheDatePeriodByType('nope'); + } + /** * Data provider for testExtractRequestedType() test method. */ @@ -229,9 +254,9 @@ class DailyPageHelperTest extends TestCase public function getStartDatesByType(): array { return [ - [DailyPageHelper::DAY, new \DateTimeImmutable('2020-10-09 04:05:06'), new \DateTime('2020-10-09 00:00:00')], - [DailyPageHelper::WEEK, new \DateTimeImmutable('2020-10-09 04:05:06'), new \DateTime('2020-10-05 00:00:00')], - [DailyPageHelper::MONTH, new \DateTimeImmutable('2020-10-09 04:05:06'), new \DateTime('2020-10-01 00:00:00')], + [DailyPageHelper::DAY, new DateTimeImmutable('2020-10-09 04:05:06'), new \DateTime('2020-10-09 00:00:00')], + [DailyPageHelper::WEEK, new DateTimeImmutable('2020-10-09 04:05:06'), new \DateTime('2020-10-05 00:00:00')], + [DailyPageHelper::MONTH, new DateTimeImmutable('2020-10-09 04:05:06'), new \DateTime('2020-10-01 00:00:00')], ]; } @@ -241,9 +266,9 @@ class DailyPageHelperTest extends TestCase public function getEndDatesByType(): array { return [ - [DailyPageHelper::DAY, new \DateTimeImmutable('2020-10-09 04:05:06'), new \DateTime('2020-10-09 23:59:59')], - [DailyPageHelper::WEEK, new \DateTimeImmutable('2020-10-09 04:05:06'), new \DateTime('2020-10-11 23:59:59')], - [DailyPageHelper::MONTH, new \DateTimeImmutable('2020-10-09 04:05:06'), new \DateTime('2020-10-31 23:59:59')], + [DailyPageHelper::DAY, new DateTimeImmutable('2020-10-09 04:05:06'), new \DateTime('2020-10-09 23:59:59')], + [DailyPageHelper::WEEK, new DateTimeImmutable('2020-10-09 04:05:06'), new \DateTime('2020-10-11 23:59:59')], + [DailyPageHelper::MONTH, new DateTimeImmutable('2020-10-09 04:05:06'), new \DateTime('2020-10-31 23:59:59')], ]; } @@ -253,11 +278,11 @@ class DailyPageHelperTest extends TestCase public function getDescriptionsByType(): array { return [ - [DailyPageHelper::DAY, $date = new \DateTimeImmutable(), 'Today - ' . $date->format('F j, Y')], - [DailyPageHelper::DAY, $date = new \DateTimeImmutable('-1 day'), 'Yesterday - ' . $date->format('F j, Y')], - [DailyPageHelper::DAY, new \DateTimeImmutable('2020-10-09 04:05:06'), 'October 9, 2020'], - [DailyPageHelper::WEEK, new \DateTimeImmutable('2020-10-09 04:05:06'), 'Week 41 (October 5, 2020)'], - [DailyPageHelper::MONTH, new \DateTimeImmutable('2020-10-09 04:05:06'), 'October, 2020'], + [DailyPageHelper::DAY, $date = new DateTimeImmutable(), 'Today - ' . $date->format('F j, Y')], + [DailyPageHelper::DAY, $date = new DateTimeImmutable('-1 day'), 'Yesterday - ' . $date->format('F j, Y')], + [DailyPageHelper::DAY, new DateTimeImmutable('2020-10-09 04:05:06'), 'October 9, 2020'], + [DailyPageHelper::WEEK, new DateTimeImmutable('2020-10-09 04:05:06'), 'Week 41 (October 5, 2020)'], + [DailyPageHelper::MONTH, new DateTimeImmutable('2020-10-09 04:05:06'), 'October, 2020'], ]; } @@ -276,7 +301,7 @@ class DailyPageHelperTest extends TestCase } /** - * Data provider for testGetDescriptionsByType() test method. + * Data provider for testGetRssLengthsByType() test method. */ public function getRssLengthsByType(): array { @@ -286,4 +311,31 @@ class DailyPageHelperTest extends TestCase [DailyPageHelper::MONTH], ]; } + + /** + * Data provider for testGetCacheDatePeriodByType() test method. + */ + public function getCacheDatePeriodByType(): array + { + return [ + [ + DailyPageHelper::DAY, + new DateTimeImmutable('2020-10-09 04:05:06'), + new \DateTime('2020-10-09 00:00:00'), + new \DateTime('2020-10-09 23:59:59'), + ], + [ + DailyPageHelper::WEEK, + new DateTimeImmutable('2020-10-09 04:05:06'), + new \DateTime('2020-10-05 00:00:00'), + new \DateTime('2020-10-11 23:59:59'), + ], + [ + DailyPageHelper::MONTH, + new DateTimeImmutable('2020-10-09 04:05:06'), + new \DateTime('2020-10-01 00:00:00'), + new \DateTime('2020-10-31 23:59:59'), + ], + ]; + } } From 151fa1e450740b08ee783e819dd42cf2c48c335c Mon Sep 17 00:00:00 2001 From: leyrer Date: Sat, 26 Dec 2020 13:45:01 +0100 Subject: [PATCH 13/33] Typo fix line 76 'Authentication' -> Authorization --- doc/md/REST-API.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/md/REST-API.md b/doc/md/REST-API.md index 01071d8e..2a36ea29 100644 --- a/doc/md/REST-API.md +++ b/doc/md/REST-API.md @@ -73,7 +73,7 @@ var_dump(getInfo($baseUrl, $secret)); ### Authentication - All requests to Shaarli's API must include a **JWT token** to verify their authenticity. -- This token must be included as an HTTP header called `Authentication: Bearer `. +- This token must be included as an HTTP header called `Authorization: Bearer `. - JWT tokens are composed by three parts, separated by a dot `.` and encoded in base64: ``` From 035a002edc7376a961e5621dc1039b082637fbc6 Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Tue, 29 Dec 2020 11:59:14 +0100 Subject: [PATCH 14/33] Fix default_colors plugin: update CSS file on color change Last update of this plugin remove the save_plugin_parameters hook. Fixes #1657 --- plugins/default_colors/default_colors.php | 14 ++++++++++++++ tests/plugins/PluginDefaultColorsTest.php | 23 +++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/plugins/default_colors/default_colors.php b/plugins/default_colors/default_colors.php index 574a0bd4..d3e1fa76 100644 --- a/plugins/default_colors/default_colors.php +++ b/plugins/default_colors/default_colors.php @@ -46,6 +46,20 @@ function default_colors_init($conf) } } +/** + * When plugin parameters are saved, we regenerate the custom CSS file with provided settings. + * + * @param array $data $_POST array + * + * @return array Updated $_POST array + */ +function hook_default_colors_save_plugin_parameters($data) +{ + default_colors_generate_css_file($data); + + return $data; +} + /** * When linklist is displayed, include default_colors CSS file. * diff --git a/tests/plugins/PluginDefaultColorsTest.php b/tests/plugins/PluginDefaultColorsTest.php index cc844c60..54e97612 100644 --- a/tests/plugins/PluginDefaultColorsTest.php +++ b/tests/plugins/PluginDefaultColorsTest.php @@ -193,4 +193,27 @@ class PluginDefaultColorsTest extends TestCase $result = default_colors_format_css_rule($data, ''); $this->assertEmpty($result); } + + /** + * Make sure that a new CSS file is generated when save_plugin_parameters hook is triggered. + */ + public function testHookSavePluginParameters(): void + { + $params = [ + 'other1' => true, + 'DEFAULT_COLORS_BACKGROUND' => 'pink', + 'other2' => ['yep'], + 'DEFAULT_COLORS_DARK_MAIN' => '', + ]; + + hook_default_colors_save_plugin_parameters($params); + $this->assertFileExists($file = 'sandbox/default_colors/default_colors.css'); + $content = file_get_contents($file); + $expected = ':root { + --background-color: pink; + +} +'; + $this->assertEquals($expected, $content); + } } From 0640c1a6db6d9a13e5d0079f0bf42497010edbc7 Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Tue, 29 Dec 2020 12:50:23 +0100 Subject: [PATCH 15/33] API: POST/PUT Link - properly parse tags string Even though the documentation specify that tags should be passed as an array, tags string is actually allowed. So this adds a proper parsing with configured separator. Related to #1651 --- application/api/ApiUtils.php | 21 +++++++-- application/api/controllers/Links.php | 12 ++++- tests/api/controllers/links/PostLinkTest.php | 48 ++++++++++++++++++++ tests/api/controllers/links/PutLinkTest.php | 48 ++++++++++++++++++++ 4 files changed, 123 insertions(+), 6 deletions(-) diff --git a/application/api/ApiUtils.php b/application/api/ApiUtils.php index 05a2840a..9228bb2d 100644 --- a/application/api/ApiUtils.php +++ b/application/api/ApiUtils.php @@ -91,13 +91,17 @@ class ApiUtils * If no URL is provided, it will generate a local note URL. * If no title is provided, it will use the URL as title. * - * @param array|null $input Request Link. - * @param bool $defaultPrivate Setting defined if a bookmark is private by default. + * @param array|null $input Request Link. + * @param bool $defaultPrivate Setting defined if a bookmark is private by default. + * @param string $tagsSeparator Tags separator loaded from the config file. * * @return Bookmark instance. */ - public static function buildBookmarkFromRequest(?array $input, bool $defaultPrivate): Bookmark - { + public static function buildBookmarkFromRequest( + ?array $input, + bool $defaultPrivate, + string $tagsSeparator + ): Bookmark { $bookmark = new Bookmark(); $url = ! empty($input['url']) ? cleanup_url($input['url']) : ''; if (isset($input['private'])) { @@ -109,6 +113,15 @@ class ApiUtils $bookmark->setTitle(! empty($input['title']) ? $input['title'] : ''); $bookmark->setUrl($url); $bookmark->setDescription(! empty($input['description']) ? $input['description'] : ''); + + // Be permissive with provided tags format + if (is_string($input['tags'] ?? null)) { + $input['tags'] = tags_str2array($input['tags'], $tagsSeparator); + } + if (is_array($input['tags'] ?? null) && count($input['tags']) === 1 && is_string($input['tags'][0])) { + $input['tags'] = tags_str2array($input['tags'][0], $tagsSeparator); + } + $bookmark->setTags(! empty($input['tags']) ? $input['tags'] : []); $bookmark->setPrivate($private); diff --git a/application/api/controllers/Links.php b/application/api/controllers/Links.php index c379b962..b83b2260 100644 --- a/application/api/controllers/Links.php +++ b/application/api/controllers/Links.php @@ -117,7 +117,11 @@ class Links extends ApiController public function postLink($request, $response) { $data = (array) ($request->getParsedBody() ?? []); - $bookmark = ApiUtils::buildBookmarkFromRequest($data, $this->conf->get('privacy.default_private_links')); + $bookmark = ApiUtils::buildBookmarkFromRequest( + $data, + $this->conf->get('privacy.default_private_links'), + $this->conf->get('general.tags_separator', ' ') + ); // duplicate by URL, return 409 Conflict if ( ! empty($bookmark->getUrl()) @@ -158,7 +162,11 @@ class Links extends ApiController $index = index_url($this->ci['environment']); $data = $request->getParsedBody(); - $requestBookmark = ApiUtils::buildBookmarkFromRequest($data, $this->conf->get('privacy.default_private_links')); + $requestBookmark = ApiUtils::buildBookmarkFromRequest( + $data, + $this->conf->get('privacy.default_private_links'), + $this->conf->get('general.tags_separator', ' ') + ); // duplicate URL on a different link, return 409 Conflict if ( ! empty($requestBookmark->getUrl()) diff --git a/tests/api/controllers/links/PostLinkTest.php b/tests/api/controllers/links/PostLinkTest.php index e12f803b..f755e2d2 100644 --- a/tests/api/controllers/links/PostLinkTest.php +++ b/tests/api/controllers/links/PostLinkTest.php @@ -229,4 +229,52 @@ class PostLinkTest extends TestCase \DateTime::createFromFormat(\DateTime::ATOM, $data['updated']) ); } + + /** + * Test link creation with a tag string provided + */ + public function testPostLinkWithTagString(): void + { + $link = [ + 'tags' => 'one two', + ]; + $env = Environment::mock([ + 'REQUEST_METHOD' => 'POST', + 'CONTENT_TYPE' => 'application/json' + ]); + + $request = Request::createFromEnvironment($env); + $request = $request->withParsedBody($link); + $response = $this->controller->postLink($request, new Response()); + + $this->assertEquals(201, $response->getStatusCode()); + $this->assertEquals('/api/v1/bookmarks/1', $response->getHeader('Location')[0]); + $data = json_decode((string) $response->getBody(), true); + $this->assertEquals(self::NB_FIELDS_LINK, count($data)); + $this->assertEquals(['one', 'two'], $data['tags']); + } + + /** + * Test link creation with a tag string provided + */ + public function testPostLinkWithTagString2(): void + { + $link = [ + 'tags' => ['one two'], + ]; + $env = Environment::mock([ + 'REQUEST_METHOD' => 'POST', + 'CONTENT_TYPE' => 'application/json' + ]); + + $request = Request::createFromEnvironment($env); + $request = $request->withParsedBody($link); + $response = $this->controller->postLink($request, new Response()); + + $this->assertEquals(201, $response->getStatusCode()); + $this->assertEquals('/api/v1/bookmarks/1', $response->getHeader('Location')[0]); + $data = json_decode((string) $response->getBody(), true); + $this->assertEquals(self::NB_FIELDS_LINK, count($data)); + $this->assertEquals(['one', 'two'], $data['tags']); + } } diff --git a/tests/api/controllers/links/PutLinkTest.php b/tests/api/controllers/links/PutLinkTest.php index 240ee323..fe24f2eb 100644 --- a/tests/api/controllers/links/PutLinkTest.php +++ b/tests/api/controllers/links/PutLinkTest.php @@ -233,4 +233,52 @@ class PutLinkTest extends \Shaarli\TestCase $this->controller->putLink($request, new Response(), ['id' => -1]); } + + /** + * Test link creation with a tag string provided + */ + public function testPutLinkWithTagString(): void + { + $link = [ + 'tags' => 'one two', + ]; + $id = '41'; + $env = Environment::mock([ + 'REQUEST_METHOD' => 'PUT', + 'CONTENT_TYPE' => 'application/json' + ]); + + $request = Request::createFromEnvironment($env); + $request = $request->withParsedBody($link); + $response = $this->controller->putLink($request, new Response(), ['id' => $id]); + + $this->assertEquals(200, $response->getStatusCode()); + $data = json_decode((string) $response->getBody(), true); + $this->assertEquals(self::NB_FIELDS_LINK, count($data)); + $this->assertEquals(['one', 'two'], $data['tags']); + } + + /** + * Test link creation with a tag string provided + */ + public function testPutLinkWithTagString2(): void + { + $link = [ + 'tags' => ['one two'], + ]; + $id = '41'; + $env = Environment::mock([ + 'REQUEST_METHOD' => 'PUT', + 'CONTENT_TYPE' => 'application/json' + ]); + + $request = Request::createFromEnvironment($env); + $request = $request->withParsedBody($link); + $response = $this->controller->putLink($request, new Response(), ['id' => $id]); + + $this->assertEquals(200, $response->getStatusCode()); + $data = json_decode((string) $response->getBody(), true); + $this->assertEquals(self::NB_FIELDS_LINK, count($data)); + $this->assertEquals(['one', 'two'], $data['tags']); + } } From bf02f8ba8e2864ff0cd12ac098b6e04e497cead9 Mon Sep 17 00:00:00 2001 From: yudete Date: Mon, 4 Jan 2021 18:55:03 +0900 Subject: [PATCH 16/33] Update Japanese translations --- inc/languages/jp/LC_MESSAGES/shaarli.po | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/inc/languages/jp/LC_MESSAGES/shaarli.po b/inc/languages/jp/LC_MESSAGES/shaarli.po index 57f42fc2..d5a83341 100644 --- a/inc/languages/jp/LC_MESSAGES/shaarli.po +++ b/inc/languages/jp/LC_MESSAGES/shaarli.po @@ -3,14 +3,14 @@ msgstr "" "Project-Id-Version: Shaarli\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-10-19 10:19+0900\n" -"PO-Revision-Date: 2020-10-19 10:25+0900\n" +"PO-Revision-Date: 2021-01-04 18:54+0900\n" "Last-Translator: yude \n" "Language-Team: Shaarli\n" "Language: ja\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Generator: Poedit 2.2.3\n" +"X-Generator: Poedit 2.4.2\n" "X-Poedit-Basepath: ../../../..\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Poedit-SourceCharset: UTF-8\n" @@ -79,8 +79,8 @@ msgid "" "php-gd extension must be loaded to use thumbnails. Thumbnails are now " "disabled. Please reload the page." msgstr "" -"サムネイルを使用するには、php-gd エクステンションが読み込まれている必要があり" -"ます。サムネイルは無効化されました。ページを再読込してください。" +"サムネイルを使用するには、php-gd 拡張機能が読み込まれている必要があります。サ" +"ムネイルは無効化されました。ページを再読込してください。" #: application/Utils.php:383 tests/UtilsTest.php:343 msgid "Setting not set" @@ -118,7 +118,7 @@ msgstr "設定を変更する権限がありません" #: application/bookmark/BookmarkFileService.php:205 msgid "This bookmarks already exists" -msgstr "このブックマークは既に存在します。" +msgstr "このブックマークは既に存在します" #: application/bookmark/BookmarkInitializer.php:39 msgid "(private bookmark with thumbnail demo)" @@ -594,8 +594,6 @@ msgstr "" "す。" #: application/legacy/LegacyUpdater.php:104 -#, fuzzy -#| msgid "Couldn't retrieve Updater class methods." msgid "Couldn't retrieve updater class methods." msgstr "アップデーターのクラスメゾットを受信できませんでした。" @@ -617,10 +615,7 @@ msgid "has an unknown file format. Nothing was imported." msgstr "は不明なファイル形式です。インポートは中止されました。" #: application/netscape/NetscapeBookmarkUtils.php:221 -#, fuzzy, php-format -#| msgid "" -#| "was successfully processed in %d seconds: %d links imported, %d links " -#| "overwritten, %d links skipped." +#, php-format msgid "" "was successfully processed in %d seconds: %d bookmarks imported, %d " "bookmarks overwritten, %d bookmarks skipped." @@ -630,7 +625,7 @@ msgstr "" #: application/plugin/PluginManager.php:124 msgid " [plugin incompatibility]: " -msgstr "[非対応のプラグイン]: " +msgstr " [非対応のプラグイン]: " #: application/plugin/exception/PluginFileNotFoundException.php:21 #, php-format From ccd1862d5f6f2c0548473466aaff7ee99f9d67d2 Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Tue, 19 Jan 2021 10:34:11 +0100 Subject: [PATCH 17/33] Inject current template name in templates Use either legacy key _PAGE_ or new 'template' one. Related to https://github.com/kalvn/Shaarli-Material/issues/118 --- .../front/controller/visitor/ShaarliVisitorController.php | 4 ++++ .../front/controller/visitor/ShaarliVisitorControllerTest.php | 3 +++ 2 files changed, 7 insertions(+) diff --git a/application/front/controller/visitor/ShaarliVisitorController.php b/application/front/controller/visitor/ShaarliVisitorController.php index ae946c59..d3f28f2f 100644 --- a/application/front/controller/visitor/ShaarliVisitorController.php +++ b/application/front/controller/visitor/ShaarliVisitorController.php @@ -56,6 +56,10 @@ abstract class ShaarliVisitorController protected function render(string $template): string { + // Legacy key that used to be injected by PluginManager + $this->assignView('_PAGE_', $template); + $this->assignView('template', $template); + $this->assignView('linkcount', $this->container->bookmarkService->count(BookmarkFilter::$ALL)); $this->assignView('privateLinkcount', $this->container->bookmarkService->count(BookmarkFilter::$PRIVATE)); diff --git a/tests/front/controller/visitor/ShaarliVisitorControllerTest.php b/tests/front/controller/visitor/ShaarliVisitorControllerTest.php index 935ec24e..7676f14d 100644 --- a/tests/front/controller/visitor/ShaarliVisitorControllerTest.php +++ b/tests/front/controller/visitor/ShaarliVisitorControllerTest.php @@ -93,6 +93,9 @@ class ShaarliVisitorControllerTest extends TestCase static::assertSame('templateName', $render); + static::assertSame('templateName', $this->assignedValues['_PAGE_']); + static::assertSame('templateName', $this->assignedValues['template']); + static::assertSame(10, $this->assignedValues['linkcount']); static::assertSame(5, $this->assignedValues['privateLinkcount']); static::assertSame(['error'], $this->assignedValues['plugin_errors']); From 9e55beebfdbc7c8db8590792479689322b92a235 Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Tue, 19 Jan 2021 11:18:56 +0100 Subject: [PATCH 18/33] Fix: error when using bulk shaare with a single URL Make sure that header metadata associated with permalink is only used in linklist template. Fixes #1686 --- tpl/default/includes.html | 2 +- tpl/vintage/includes.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tpl/default/includes.html b/tpl/default/includes.html index 3e3fb664..6be2a2ea 100644 --- a/tpl/default/includes.html +++ b/tpl/default/includes.html @@ -19,7 +19,7 @@ {/if} -{if="! empty($links) && count($links) === 1"} +{if="$template === 'linklist' && ! empty($links) && count($links) === 1"} {$link=reset($links)} diff --git a/tpl/vintage/includes.html b/tpl/vintage/includes.html index 2ce9da42..4fd78467 100644 --- a/tpl/vintage/includes.html +++ b/tpl/vintage/includes.html @@ -16,7 +16,7 @@ {if="is_file('data/user.css')"}{/if} -{if="! empty($links) && count($links) === 1"} +{if="$template === 'linklist' && ! empty($links) && count($links) === 1"} {$link=reset($links)} From 8fbc29de0252c462884605d4f691b6746e524fc9 Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Tue, 19 Jan 2021 11:35:42 +0100 Subject: [PATCH 19/33] Fix: bulk add - use unique HTML ID Use links loop ID to make ID unique and fix browser labels behaviour. Fixes #1685 --- tpl/default/editlink.batch.html | 1 + tpl/default/editlink.html | 23 ++++++++++++----------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/tpl/default/editlink.batch.html b/tpl/default/editlink.batch.html index b1f8e5bd..973a5ccc 100644 --- a/tpl/default/editlink.batch.html +++ b/tpl/default/editlink.batch.html @@ -20,6 +20,7 @@ {loop="$links"} + {$batchId=$key} {include="editlink"} {/loop} diff --git a/tpl/default/editlink.html b/tpl/default/editlink.html index 83e541fd..a5828c75 100644 --- a/tpl/default/editlink.html +++ b/tpl/default/editlink.html @@ -1,3 +1,4 @@ +{$batchId=isset($batchId) ? $batchId : ''} {if="empty($batch_mode)"} @@ -10,7 +11,7 @@ {ignore}Lil hack: when included in a loop in batch mode, `$value` is assigned by RainTPL with template vars.{/ignore} {function="extract($value) ? '' : ''"} {/if} -