From 66063ed1a18d739b1a60bfb163d8656417a4c529 Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Sat, 30 May 2020 14:00:06 +0200 Subject: Process configure page through Slim controller --- .../front/controller/admin/ConfigureController.php | 120 ++++++++++ application/render/PageBuilder.php | 4 + doc/md/Translations.md | 2 +- index.php | 86 +------ .../controller/admin/ConfigureControllerTest.php | 252 +++++++++++++++++++++ .../admin/FrontAdminControllerMockHelper.php | 23 ++ .../controller/visitor/DailyControllerTest.php | 27 +-- .../visitor/FrontControllerMockHelper.php | 10 +- tpl/default/configure.html | 2 +- tpl/default/tools.html | 2 +- tpl/vintage/configure.html | 2 +- tpl/vintage/tools.html | 2 +- 12 files changed, 427 insertions(+), 105 deletions(-) create mode 100644 application/front/controller/admin/ConfigureController.php create mode 100644 tests/front/controller/admin/ConfigureControllerTest.php diff --git a/application/front/controller/admin/ConfigureController.php b/application/front/controller/admin/ConfigureController.php new file mode 100644 index 00000000..b1d32270 --- /dev/null +++ b/application/front/controller/admin/ConfigureController.php @@ -0,0 +1,120 @@ +assignView('title', $this->container->conf->get('general.title', 'Shaarli')); + $this->assignView('theme', $this->container->conf->get('resource.theme')); + $this->assignView( + 'theme_available', + ThemeUtils::getThemes($this->container->conf->get('resource.raintpl_tpl')) + ); + $this->assignView('formatter_available', ['default', 'markdown']); + list($continents, $cities) = generateTimeZoneData( + timezone_identifiers_list(), + $this->container->conf->get('general.timezone') + ); + $this->assignView('continents', $continents); + $this->assignView('cities', $cities); + $this->assignView('retrieve_description', $this->container->conf->get('general.retrieve_description', false)); + $this->assignView('private_links_default', $this->container->conf->get('privacy.default_private_links', false)); + $this->assignView( + 'session_protection_disabled', + $this->container->conf->get('security.session_protection_disabled', false) + ); + $this->assignView('enable_rss_permalinks', $this->container->conf->get('feed.rss_permalinks', false)); + $this->assignView('enable_update_check', $this->container->conf->get('updates.check_updates', true)); + $this->assignView('hide_public_links', $this->container->conf->get('privacy.hide_public_links', false)); + $this->assignView('api_enabled', $this->container->conf->get('api.enabled', true)); + $this->assignView('api_secret', $this->container->conf->get('api.secret')); + $this->assignView('languages', Languages::getAvailableLanguages()); + $this->assignView('gd_enabled', extension_loaded('gd')); + $this->assignView('thumbnails_mode', $this->container->conf->get('thumbnails.mode', Thumbnailer::MODE_NONE)); + $this->assignView('pagetitle', t('Configure') .' - '. $this->container->conf->get('general.title', 'Shaarli')); + + return $response->write($this->render('configure')); + } + + /** + * POST /configure - Update Shaarli's configuration + */ + public function save(Request $request, Response $response): Response + { + $this->checkToken($request); + + $continent = $request->getParam('continent'); + $city = $request->getParam('city'); + $tz = 'UTC'; + if (null !== $continent && null !== $city && isTimeZoneValid($continent, $city)) { + $tz = $continent . '/' . $city; + } + + $this->container->conf->set('general.timezone', $tz); + $this->container->conf->set('general.title', escape($request->getParam('title'))); + $this->container->conf->set('general.header_link', escape($request->getParam('titleLink'))); + $this->container->conf->set('general.retrieve_description', !empty($request->getParam('retrieveDescription'))); + $this->container->conf->set('resource.theme', escape($request->getParam('theme'))); + $this->container->conf->set( + 'security.session_protection_disabled', + !empty($request->getParam('disablesessionprotection')) + ); + $this->container->conf->set( + 'privacy.default_private_links', + !empty($request->getParam('privateLinkByDefault')) + ); + $this->container->conf->set('feed.rss_permalinks', !empty($request->getParam('enableRssPermalinks'))); + $this->container->conf->set('updates.check_updates', !empty($request->getParam('updateCheck'))); + $this->container->conf->set('privacy.hide_public_links', !empty($request->getParam('hidePublicLinks'))); + $this->container->conf->set('api.enabled', !empty($request->getParam('enableApi'))); + $this->container->conf->set('api.secret', escape($request->getParam('apiSecret'))); + $this->container->conf->set('formatter', escape($request->getParam('formatter'))); + + if (!empty($request->getParam('language'))) { + $this->container->conf->set('translation.language', escape($request->getParam('language'))); + } + + $thumbnailsMode = extension_loaded('gd') ? $request->getParam('enableThumbnails') : Thumbnailer::MODE_NONE; + if ($thumbnailsMode !== Thumbnailer::MODE_NONE + && $thumbnailsMode !== $this->container->conf->get('thumbnails.mode', Thumbnailer::MODE_NONE) + ) { + $this->saveWarningMessage(t( + 'You have enabled or changed thumbnails mode. ' + .'Please synchronize them.' + )); + } + $this->container->conf->set('thumbnails.mode', $thumbnailsMode); + + try { + $this->container->conf->write($this->container->loginManager->isLoggedIn()); + $this->container->history->updateSettings(); + $this->container->pageCacheManager->invalidateCaches(); + } catch (Throwable $e) { + // TODO: translation + stacktrace + $this->saveErrorMessage('ERROR while writing config file after configuration update.'); + } + + $this->saveSuccessMessage(t('Configuration was saved.')); + + return $response->withRedirect('./configure'); + } +} diff --git a/application/render/PageBuilder.php b/application/render/PageBuilder.php index 264cd33b..d90ed58b 100644 --- a/application/render/PageBuilder.php +++ b/application/render/PageBuilder.php @@ -143,6 +143,10 @@ class PageBuilder $this->tpl->assign('conf', $this->conf); } + /** + * Affect variable after controller processing. + * Used for alert messages. + */ protected function finalize(): void { // TODO: use the SessionManager diff --git a/doc/md/Translations.md b/doc/md/Translations.md index 38878940..9a16075a 100644 --- a/doc/md/Translations.md +++ b/doc/md/Translations.md @@ -35,7 +35,7 @@ http:///?nonope http:///?do=addlink http:///?do=changepasswd http:///?do=changetag -http:///?do=configure +http:///configure http:///tools http:///daily http:///?post diff --git a/index.php b/index.php index ae56b800..50c0634a 100644 --- a/index.php +++ b/index.php @@ -513,88 +513,8 @@ function renderPage($conf, $pluginManager, $bookmarkService, $history, $sessionM // -------- User wants to change configuration if ($targetPage == Router::$PAGE_CONFIGURE) { - if (!empty($_POST['title'])) { - if (!$sessionManager->checkToken($_POST['token'])) { - die(t('Wrong token.')); // Go away! - } - $tz = 'UTC'; - if (!empty($_POST['continent']) && !empty($_POST['city']) - && isTimeZoneValid($_POST['continent'], $_POST['city']) - ) { - $tz = $_POST['continent'] . '/' . $_POST['city']; - } - $conf->set('general.timezone', $tz); - $conf->set('general.title', escape($_POST['title'])); - $conf->set('general.header_link', escape($_POST['titleLink'])); - $conf->set('general.retrieve_description', !empty($_POST['retrieveDescription'])); - $conf->set('resource.theme', escape($_POST['theme'])); - $conf->set('security.session_protection_disabled', !empty($_POST['disablesessionprotection'])); - $conf->set('privacy.default_private_links', !empty($_POST['privateLinkByDefault'])); - $conf->set('feed.rss_permalinks', !empty($_POST['enableRssPermalinks'])); - $conf->set('updates.check_updates', !empty($_POST['updateCheck'])); - $conf->set('privacy.hide_public_links', !empty($_POST['hidePublicLinks'])); - $conf->set('api.enabled', !empty($_POST['enableApi'])); - $conf->set('api.secret', escape($_POST['apiSecret'])); - $conf->set('formatter', escape($_POST['formatter'])); - - if (! empty($_POST['language'])) { - $conf->set('translation.language', escape($_POST['language'])); - } - - $thumbnailsMode = extension_loaded('gd') ? $_POST['enableThumbnails'] : Thumbnailer::MODE_NONE; - if ($thumbnailsMode !== Thumbnailer::MODE_NONE - && $thumbnailsMode !== $conf->get('thumbnails.mode', Thumbnailer::MODE_NONE) - ) { - $_SESSION['warnings'][] = t( - 'You have enabled or changed thumbnails mode. ' - .'Please synchronize them.' - ); - } - $conf->set('thumbnails.mode', $thumbnailsMode); - - try { - $conf->write($loginManager->isLoggedIn()); - $history->updateSettings(); - $pageCacheManager->invalidateCaches(); - } catch (Exception $e) { - error_log( - 'ERROR while writing config file after configuration update.' . PHP_EOL . - $e->getMessage() - ); - - // TODO: do not handle exceptions/errors in JS. - echo ''; - exit; - } - echo ''; - exit; - } else { - // Show the configuration form. - $PAGE->assign('title', $conf->get('general.title')); - $PAGE->assign('theme', $conf->get('resource.theme')); - $PAGE->assign('theme_available', ThemeUtils::getThemes($conf->get('resource.raintpl_tpl'))); - $PAGE->assign('formatter_available', ['default', 'markdown']); - list($continents, $cities) = generateTimeZoneData( - timezone_identifiers_list(), - $conf->get('general.timezone') - ); - $PAGE->assign('continents', $continents); - $PAGE->assign('cities', $cities); - $PAGE->assign('retrieve_description', $conf->get('general.retrieve_description')); - $PAGE->assign('private_links_default', $conf->get('privacy.default_private_links', false)); - $PAGE->assign('session_protection_disabled', $conf->get('security.session_protection_disabled', false)); - $PAGE->assign('enable_rss_permalinks', $conf->get('feed.rss_permalinks', false)); - $PAGE->assign('enable_update_check', $conf->get('updates.check_updates', true)); - $PAGE->assign('hide_public_links', $conf->get('privacy.hide_public_links', false)); - $PAGE->assign('api_enabled', $conf->get('api.enabled', true)); - $PAGE->assign('api_secret', $conf->get('api.secret')); - $PAGE->assign('languages', Languages::getAvailableLanguages()); - $PAGE->assign('gd_enabled', extension_loaded('gd')); - $PAGE->assign('thumbnails_mode', $conf->get('thumbnails.mode', Thumbnailer::MODE_NONE)); - $PAGE->assign('pagetitle', t('Configure') .' - '. $conf->get('general.title', 'Shaarli')); - $PAGE->renderPage('configure'); - exit; - } + header('Location: ./configure'); + exit; } // -------- User wants to rename a tag or delete it @@ -1458,6 +1378,8 @@ $app->group('', function () { $this->get('/tools', '\Shaarli\Front\Controller\Admin\ToolsController:index')->setName('tools'); $this->get('/password', '\Shaarli\Front\Controller\Admin\PasswordController:index')->setName('password'); $this->post('/password', '\Shaarli\Front\Controller\Admin\PasswordController:change')->setName('changePassword'); + $this->get('/configure', '\Shaarli\Front\Controller\Admin\ConfigureController:index')->setName('configure'); + $this->post('/configure', '\Shaarli\Front\Controller\Admin\ConfigureController:save')->setName('saveConfigure'); $this ->get('/links-per-page', '\Shaarli\Front\Controller\Admin\SessionFilterController:linksPerPage') diff --git a/tests/front/controller/admin/ConfigureControllerTest.php b/tests/front/controller/admin/ConfigureControllerTest.php new file mode 100644 index 00000000..40304a18 --- /dev/null +++ b/tests/front/controller/admin/ConfigureControllerTest.php @@ -0,0 +1,252 @@ +createContainer(); + + $this->controller = new ConfigureController($this->container); + } + + /** + * Test displaying configure page - it should display all config variables + */ + public function testIndex(): void + { + $assignedVariables = []; + $this->assignTemplateVars($assignedVariables); + + $request = $this->createMock(Request::class); + $response = new Response(); + + $this->container->conf = $this->createMock(ConfigManager::class); + $this->container->conf->method('get')->willReturnCallback(function (string $key) { + return $key; + }); + + $result = $this->controller->index($request, $response); + + static::assertSame(200, $result->getStatusCode()); + static::assertSame('configure', (string) $result->getBody()); + + static::assertSame('Configure - general.title', $assignedVariables['pagetitle']); + static::assertSame('general.title', $assignedVariables['title']); + static::assertSame('resource.theme', $assignedVariables['theme']); + static::assertEmpty($assignedVariables['theme_available']); + static::assertSame(['default', 'markdown'], $assignedVariables['formatter_available']); + static::assertNotEmpty($assignedVariables['continents']); + static::assertNotEmpty($assignedVariables['cities']); + static::assertSame('general.retrieve_description', $assignedVariables['retrieve_description']); + static::assertSame('privacy.default_private_links', $assignedVariables['private_links_default']); + static::assertSame('security.session_protection_disabled', $assignedVariables['session_protection_disabled']); + static::assertSame('feed.rss_permalinks', $assignedVariables['enable_rss_permalinks']); + static::assertSame('updates.check_updates', $assignedVariables['enable_update_check']); + 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(4, $assignedVariables['languages']); + static::assertArrayHasKey('gd_enabled', $assignedVariables); + static::assertSame('thumbnails.mode', $assignedVariables['thumbnails_mode']); + } + + /** + * Test posting a new config - make sure that everything is saved properly, without errors. + */ + public function testSaveNewConfig(): void + { + $session = []; + $this->assignSessionVars($session); + + $parameters = [ + 'token' => 'token', + 'continent' => 'Europe', + 'city' => 'Moscow', + 'title' => 'Shaarli', + 'titleLink' => './', + 'retrieveDescription' => 'on', + 'theme' => 'vintage', + 'disablesessionprotection' => null, + 'privateLinkByDefault' => true, + 'enableRssPermalinks' => true, + 'updateCheck' => false, + 'hidePublicLinks' => 'on', + 'enableApi' => 'on', + 'apiSecret' => 'abcdef', + 'formatter' => 'markdown', + 'language' => 'fr', + 'enableThumbnails' => Thumbnailer::MODE_NONE, + ]; + + $parametersConfigMapping = [ + 'general.timezone' => $parameters['continent'] . '/' . $parameters['city'], + 'general.title' => $parameters['title'], + 'general.header_link' => $parameters['titleLink'], + 'general.retrieve_description' => !!$parameters['retrieveDescription'], + 'resource.theme' => $parameters['theme'], + 'security.session_protection_disabled' => !!$parameters['disablesessionprotection'], + 'privacy.default_private_links' => !!$parameters['privateLinkByDefault'], + 'feed.rss_permalinks' => !!$parameters['enableRssPermalinks'], + 'updates.check_updates' => !!$parameters['updateCheck'], + 'privacy.hide_public_links' => !!$parameters['hidePublicLinks'], + 'api.enabled' => !!$parameters['enableApi'], + 'api.secret' => $parameters['apiSecret'], + 'formatter' => $parameters['formatter'], + 'translation.language' => $parameters['language'], + 'thumbnails.mode' => $parameters['enableThumbnails'], + ]; + + $request = $this->createMock(Request::class); + $request + ->expects(static::atLeastOnce()) + ->method('getParam')->willReturnCallback(function (string $key) use ($parameters) { + if (false === array_key_exists($key, $parameters)) { + static::fail('unknown key: ' . $key); + } + + return $parameters[$key]; + } + ); + + $response = new Response(); + + $this->container->conf = $this->createMock(ConfigManager::class); + $this->container->conf + ->expects(static::atLeastOnce()) + ->method('set') + ->willReturnCallback(function (string $key, $value) use ($parametersConfigMapping): void { + if (false === array_key_exists($key, $parametersConfigMapping)) { + static::fail('unknown key: ' . $key); + } + + static::assertSame($parametersConfigMapping[$key], $value); + } + ); + + $result = $this->controller->save($request, $response); + static::assertSame(302, $result->getStatusCode()); + static::assertSame(['./configure'], $result->getHeader('Location')); + + static::assertArrayNotHasKey(SessionManager::KEY_WARNING_MESSAGES, $session); + static::assertArrayNotHasKey(SessionManager::KEY_ERROR_MESSAGES, $session); + static::assertArrayHasKey(SessionManager::KEY_SUCCESS_MESSAGES, $session); + static::assertSame(['Configuration was saved.'], $session[SessionManager::KEY_SUCCESS_MESSAGES]); + } + + /** + * Test posting a new config - wrong token. + */ + public function testSaveNewConfigWrongToken(): void + { + $this->container->sessionManager = $this->createMock(SessionManager::class); + $this->container->sessionManager->method('checkToken')->willReturn(false); + + $this->container->conf->expects(static::never())->method('set'); + $this->container->conf->expects(static::never())->method('write'); + + $request = $this->createMock(Request::class); + $response = new Response(); + + $this->expectException(WrongTokenException::class); + + $this->controller->save($request, $response); + } + + /** + * Test posting a new config - thumbnail activation. + */ + public function testSaveNewConfigThumbnailsActivation(): void + { + $session = []; + $this->assignSessionVars($session); + + $request = $this->createMock(Request::class); + $request + ->expects(static::atLeastOnce()) + ->method('getParam')->willReturnCallback(function (string $key) { + if ('enableThumbnails' === $key) { + return Thumbnailer::MODE_ALL; + } + + return $key; + }) + ; + $response = new Response(); + + $result = $this->controller->save($request, $response); + + static::assertSame(302, $result->getStatusCode()); + static::assertSame(['./configure'], $result->getHeader('Location')); + + static::assertArrayNotHasKey(SessionManager::KEY_ERROR_MESSAGES, $session); + static::assertArrayHasKey(SessionManager::KEY_WARNING_MESSAGES, $session); + static::assertStringContainsString( + 'You have enabled or changed thumbnails mode', + $session[SessionManager::KEY_WARNING_MESSAGES][0] + ); + static::assertArrayHasKey(SessionManager::KEY_SUCCESS_MESSAGES, $session); + static::assertSame(['Configuration was saved.'], $session[SessionManager::KEY_SUCCESS_MESSAGES]); + } + + /** + * Test posting a new config - thumbnail activation. + */ + public function testSaveNewConfigThumbnailsAlreadyActive(): void + { + $session = []; + $this->assignSessionVars($session); + + $request = $this->createMock(Request::class); + $request + ->expects(static::atLeastOnce()) + ->method('getParam')->willReturnCallback(function (string $key) { + if ('enableThumbnails' === $key) { + return Thumbnailer::MODE_ALL; + } + + return $key; + }) + ; + $response = new Response(); + + $this->container->conf = $this->createMock(ConfigManager::class); + $this->container->conf + ->expects(static::atLeastOnce()) + ->method('get') + ->willReturnCallback(function (string $key): string { + if ('thumbnails.mode' === $key) { + return Thumbnailer::MODE_ALL; + } + + return $key; + }) + ; + + $result = $this->controller->save($request, $response); + + static::assertSame(302, $result->getStatusCode()); + static::assertSame(['./configure'], $result->getHeader('Location')); + + static::assertArrayNotHasKey(SessionManager::KEY_ERROR_MESSAGES, $session); + static::assertArrayNotHasKey(SessionManager::KEY_WARNING_MESSAGES, $session); + static::assertArrayHasKey(SessionManager::KEY_SUCCESS_MESSAGES, $session); + static::assertSame(['Configuration was saved.'], $session[SessionManager::KEY_SUCCESS_MESSAGES]); + } +} diff --git a/tests/front/controller/admin/FrontAdminControllerMockHelper.php b/tests/front/controller/admin/FrontAdminControllerMockHelper.php index bd40c0c7..2b9f2ef1 100644 --- a/tests/front/controller/admin/FrontAdminControllerMockHelper.php +++ b/tests/front/controller/admin/FrontAdminControllerMockHelper.php @@ -6,6 +6,7 @@ namespace Shaarli\Front\Controller\Admin; use Shaarli\Container\ShaarliTestContainer; use Shaarli\Front\Controller\Visitor\FrontControllerMockHelper; +use Shaarli\History; /** * Trait FrontControllerMockHelper @@ -27,7 +28,29 @@ trait FrontAdminControllerMockHelper { $this->parentCreateContainer(); + $this->container->history = $this->createMock(History::class); + $this->container->loginManager->method('isLoggedIn')->willReturn(true); $this->container->sessionManager->method('checkToken')->willReturn(true); } + + + /** + * Pass a reference of an array which will be populated by `sessionManager->setSessionParameter` + * calls during execution. + * + * @param mixed $variables Array reference to populate. + */ + protected function assignSessionVars(array &$variables): void + { + $this->container->sessionManager + ->expects(static::atLeastOnce()) + ->method('setSessionParameter') + ->willReturnCallback(function ($key, $value) use (&$variables) { + $variables[$key] = $value; + + return $this->container->sessionManager; + }) + ; + } } diff --git a/tests/front/controller/visitor/DailyControllerTest.php b/tests/front/controller/visitor/DailyControllerTest.php index 872420fd..b802c62c 100644 --- a/tests/front/controller/visitor/DailyControllerTest.php +++ b/tests/front/controller/visitor/DailyControllerTest.php @@ -57,20 +57,20 @@ class DailyControllerTest extends TestCase (new Bookmark()) ->setId(1) ->setUrl('http://url.tld') - ->setTitle(static::generateContent(50)) - ->setDescription(static::generateContent(500)) + ->setTitle(static::generateString(50)) + ->setDescription(static::generateString(500)) , (new Bookmark()) ->setId(2) ->setUrl('http://url2.tld') - ->setTitle(static::generateContent(50)) - ->setDescription(static::generateContent(500)) + ->setTitle(static::generateString(50)) + ->setDescription(static::generateString(500)) , (new Bookmark()) ->setId(3) ->setUrl('http://url3.tld') - ->setTitle(static::generateContent(50)) - ->setDescription(static::generateContent(500)) + ->setTitle(static::generateString(50)) + ->setDescription(static::generateString(500)) , ]; }) @@ -194,8 +194,8 @@ class DailyControllerTest extends TestCase (new Bookmark()) ->setId(1) ->setUrl('http://url.tld') - ->setTitle(static::generateContent(50)) - ->setDescription(static::generateContent(500)) + ->setTitle(static::generateString(50)) + ->setDescription(static::generateString(500)) , ]; }) @@ -267,8 +267,8 @@ class DailyControllerTest extends TestCase (new Bookmark()) ->setId(2) ->setUrl('http://url.tld') - ->setTitle(static::generateContent(50)) - ->setDescription(static::generateContent(5000)) + ->setTitle(static::generateString(50)) + ->setDescription(static::generateString(5000)) , (new Bookmark())->setId(3)->setUrl('http://url.tld')->setTitle('title'), (new Bookmark())->setId(4)->setUrl('http://url.tld')->setTitle('title'), @@ -473,11 +473,4 @@ class DailyControllerTest extends TestCase static::assertFalse($assignedVariables['hide_timestamps']); static::assertCount(0, $assignedVariables['days']); } - - protected static function generateContent(int $length): string - { - // bin2hex(random_bytes) generates string twice as long as given parameter - $length = (int) ceil($length / 2); - return bin2hex(random_bytes($length)); - } } diff --git a/tests/front/controller/visitor/FrontControllerMockHelper.php b/tests/front/controller/visitor/FrontControllerMockHelper.php index d16b6949..fecd0c82 100644 --- a/tests/front/controller/visitor/FrontControllerMockHelper.php +++ b/tests/front/controller/visitor/FrontControllerMockHelper.php @@ -42,7 +42,7 @@ trait FrontControllerMockHelper // Config $this->container->conf = $this->createMock(ConfigManager::class); $this->container->conf->method('get')->willReturnCallback(function (string $parameter, $default) { - return $default; + return $default === null ? $parameter : $default; }); // PageBuilder @@ -101,6 +101,14 @@ trait FrontControllerMockHelper ; } + protected static function generateString(int $length): string + { + // bin2hex(random_bytes) generates string twice as long as given parameter + $length = (int) ceil($length / 2); + + return bin2hex(random_bytes($length)); + } + /** * Force to be used in PHPUnit context. */ diff --git a/tpl/default/configure.html b/tpl/default/configure.html index 9b6a9c46..46bef052 100644 --- a/tpl/default/configure.html +++ b/tpl/default/configure.html @@ -35,7 +35,7 @@
diff --git a/tpl/default/tools.html b/tpl/default/tools.html index 4a490963..0135c480 100644 --- a/tpl/default/tools.html +++ b/tpl/default/tools.html @@ -11,7 +11,7 @@

{'Settings'|t}

diff --git a/tpl/vintage/configure.html b/tpl/vintage/configure.html index 8d20ea80..a87fdce7 100644 --- a/tpl/vintage/configure.html +++ b/tpl/vintage/configure.html @@ -16,7 +16,7 @@ Home link:
+ for="titleLink">(default value is: ./) diff --git a/tpl/vintage/tools.html b/tpl/vintage/tools.html index 174dc88f..0d8fcdec 100644 --- a/tpl/vintage/tools.html +++ b/tpl/vintage/tools.html @@ -5,7 +5,7 @@