From: ArthurHoaro Date: Sun, 26 Jan 2020 10:41:10 +0000 (+0100) Subject: Render login page through Slim controller (#1401) X-Git-Tag: v0.12.0-beta~16 X-Git-Url: https://git.immae.eu/?a=commitdiff_plain;h=c653ae3bfb11f663a52f55817e6d02a66d0852c8;hp=1410dce2db310e71b5e683b0871c2f28d8807844;p=github%2Fshaarli%2FShaarli.git Render login page through Slim controller (#1401) Render login page through Slim controller --- diff --git a/application/Utils.php b/application/Utils.php index 56f5b9a2..4b7fc546 100644 --- a/application/Utils.php +++ b/application/Utils.php @@ -159,7 +159,7 @@ function checkDateFormat($format, $string) */ function generateLocation($referer, $host, $loopTerms = array()) { - $finalReferer = '?'; + $finalReferer = './?'; // No referer if it contains any value in $loopCriteria. foreach (array_filter($loopTerms) as $value) { diff --git a/application/container/ContainerBuilder.php b/application/container/ContainerBuilder.php new file mode 100644 index 00000000..e2c78ccc --- /dev/null +++ b/application/container/ContainerBuilder.php @@ -0,0 +1,81 @@ +conf = $conf; + $this->session = $session; + $this->login = $login; + } + + public function build(): ShaarliContainer + { + $container = new ShaarliContainer(); + $container['conf'] = $this->conf; + $container['sessionManager'] = $this->session; + $container['loginManager'] = $this->login; + $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')); + }; + + $container['bookmarkService'] = function (ShaarliContainer $container): BookmarkServiceInterface { + return new BookmarkFileService( + $container->conf, + $container->history, + $container->loginManager->isLoggedIn() + ); + }; + + $container['pageBuilder'] = function (ShaarliContainer $container): PageBuilder { + return new PageBuilder( + $container->conf, + $container->sessionManager->getSession(), + $container->bookmarkService, + $container->sessionManager->generateToken(), + $container->loginManager->isLoggedIn() + ); + }; + + $container['pluginManager'] = function (ShaarliContainer $container): PluginManager { + return new PluginManager($container->conf); + }; + + return $container; + } +} diff --git a/application/container/ShaarliContainer.php b/application/container/ShaarliContainer.php new file mode 100644 index 00000000..3fa9116e --- /dev/null +++ b/application/container/ShaarliContainer.php @@ -0,0 +1,30 @@ +container = $container; + } + + /** + * Middleware execution: + * - execute the controller + * - return the response + * + * In case of error, the error template will be displayed with the exception message. + * + * @param Request $request Slim request + * @param Response $response Slim response + * @param callable $next Next action + * + * @return Response response. + */ + public function __invoke(Request $request, Response $response, callable $next) + { + try { + $response = $next($request, $response); + } catch (ShaarliException $e) { + $this->container->pageBuilder->assign('message', $e->getMessage()); + if ($this->container->conf->get('dev.debug', false)) { + $this->container->pageBuilder->assign( + 'stacktrace', + nl2br(get_class($this) .': '. $e->getTraceAsString()) + ); + } + + $response = $response->withStatus($e->getCode()); + $response = $response->write($this->container->pageBuilder->render('error')); + } + + return $response; + } +} diff --git a/application/front/controllers/LoginController.php b/application/front/controllers/LoginController.php new file mode 100644 index 00000000..ae3599e0 --- /dev/null +++ b/application/front/controllers/LoginController.php @@ -0,0 +1,48 @@ +container->loginManager->isLoggedIn() + || $this->container->conf->get('security.open_shaarli', false) + ) { + return $response->withRedirect('./'); + } + + $userCanLogin = $this->container->loginManager->canLogin($request->getServerParams()); + if ($userCanLogin !== true) { + throw new LoginBannedException(); + } + + if ($request->getParam('username') !== null) { + $this->assignView('username', escape($request->getParam('username'))); + } + + $this + ->assignView('returnurl', escape($request->getServerParam('HTTP_REFERER'))) + ->assignView('remember_user_default', $this->container->conf->get('privacy.remember_user_default', true)) + ->assignView('pagetitle', t('Login') .' - '. $this->container->conf->get('general.title', 'Shaarli')) + ; + + return $response->write($this->render('loginform')); + } +} diff --git a/application/front/controllers/ShaarliController.php b/application/front/controllers/ShaarliController.php new file mode 100644 index 00000000..2b828588 --- /dev/null +++ b/application/front/controllers/ShaarliController.php @@ -0,0 +1,69 @@ +container = $container; + } + + /** + * Assign variables to RainTPL template through the PageBuilder. + * + * @param mixed $value Value to assign to the template + */ + protected function assignView(string $name, $value): self + { + $this->container->pageBuilder->assign($name, $value); + + return $this; + } + + protected function render(string $template): string + { + $this->assignView('linkcount', $this->container->bookmarkService->count(BookmarkFilter::$ALL)); + $this->assignView('privateLinkcount', $this->container->bookmarkService->count(BookmarkFilter::$PRIVATE)); + $this->assignView('plugin_errors', $this->container->pluginManager->getErrors()); + + $this->executeDefaultHooks($template); + + return $this->container->pageBuilder->render($template); + } + + /** + * Call plugin hooks for header, footer and includes, specifying which page will be rendered. + * Then assign generated data to RainTPL. + */ + protected function executeDefaultHooks(string $template): void + { + $common_hooks = [ + 'includes', + 'header', + 'footer', + ]; + + foreach ($common_hooks as $name) { + $plugin_data = []; + $this->container->pluginManager->executeHooks( + 'render_' . $name, + $plugin_data, + [ + 'target' => $template, + 'loggedin' => $this->container->loginManager->isLoggedIn() + ] + ); + $this->assignView('plugins_' . $name, $plugin_data); + } + } +} diff --git a/application/front/exceptions/LoginBannedException.php b/application/front/exceptions/LoginBannedException.php new file mode 100644 index 00000000..b31a4a14 --- /dev/null +++ b/application/front/exceptions/LoginBannedException.php @@ -0,0 +1,15 @@ +tpl->draw($page); } + /** + * Render a specific page as string (using a template file). + * e.g. $pb->render('picwall'); + * + * @param string $page Template filename (without extension). + * + * @return string Processed template content + */ + public function render(string $page): string + { + if ($this->tpl === false) { + $this->initialize(); + } + + return $this->tpl->draw($page, true); + } + /** * Render a 404 page (uses the template : tpl/404.tpl) * usage: $PAGE->render404('The link was deleted') diff --git a/application/security/SessionManager.php b/application/security/SessionManager.php index b8b8ab8d..994fcbe5 100644 --- a/application/security/SessionManager.php +++ b/application/security/SessionManager.php @@ -196,4 +196,10 @@ class SessionManager } return true; } + + /** @return array Local reference to the global $_SESSION array */ + public function getSession(): array + { + return $this->session; + } } diff --git a/assets/default/scss/shaarli.scss b/assets/default/scss/shaarli.scss index cd5dd9e6..243ab1b2 100644 --- a/assets/default/scss/shaarli.scss +++ b/assets/default/scss/shaarli.scss @@ -1236,8 +1236,19 @@ form { color: $dark-grey; } -.page404-container { +.page-error-container { color: $dark-grey; + + h2 { + margin: 70px 0 25px; + } + + pre { + margin: 0 20%; + padding: 20px 0; + text-align: left; + line-height: .7em; + } } // EDIT LINK diff --git a/composer.json b/composer.json index ada06a74..6b670fa2 100644 --- a/composer.json +++ b/composer.json @@ -48,9 +48,13 @@ "Shaarli\\Bookmark\\Exception\\": "application/bookmark/exception", "Shaarli\\Config\\": "application/config/", "Shaarli\\Config\\Exception\\": "application/config/exception", + "Shaarli\\Container\\": "application/container", "Shaarli\\Exceptions\\": "application/exceptions", "Shaarli\\Feed\\": "application/feed", "Shaarli\\Formatter\\": "application/formatter", + "Shaarli\\Front\\": "application/front", + "Shaarli\\Front\\Controller\\": "application/front/controllers", + "Shaarli\\Front\\Exception\\": "application/front/exceptions", "Shaarli\\Http\\": "application/http", "Shaarli\\Legacy\\": "application/legacy", "Shaarli\\Netscape\\": "application/netscape", diff --git a/doc/md/Translations.md b/doc/md/Translations.md index c7d33855..58b92da3 100644 --- a/doc/md/Translations.md +++ b/doc/md/Translations.md @@ -7,8 +7,8 @@ Note that only the `default` theme supports translations. ### Contributing -We encourage the community to contribute to Shaarli's translation either by improving existing -translations or submitting a new language. +We encourage the community to contribute to Shaarli's translation either by improving existing +translations or submitting a new language. Contributing to the translation does not require development skill. @@ -21,8 +21,8 @@ First, install [Poedit](https://poedit.net/) tool. Poedit will extract strings to translate from the PHP source code. -**Important**: due to the usage of a template engine, it's important to generate PHP cache files to extract -every translatable string. +**Important**: due to the usage of a template engine, it's important to generate PHP cache files to extract +every translatable string. You can either use [this script](https://gist.github.com/ArthurHoaro/5d0323f758ab2401ef444a53f54e9a07) (recommended) or visit every template page in your browser to generate cache files, while logged in. @@ -41,7 +41,7 @@ http:///?do=daily http:///?post http:///?do=export http:///?do=import -http:///?do=login +http:///login http:///?do=picwall http:///?do=pluginadmin http:///?do=tagcloud @@ -50,8 +50,8 @@ http:///?do=taglist #### Improve existing translation -In Poedit, click on "Edit a Translation", and from Shaarli's directory open -`inc/languages//LC_MESSAGES/shaarli.po`. +In Poedit, click on "Edit a Translation", and from Shaarli's directory open +`inc/languages//LC_MESSAGES/shaarli.po`. The existing list of translatable strings should have been loaded, then click on the "Update" button. @@ -63,20 +63,20 @@ Save when you're done, then you can submit a pull request containing the updated #### Add a new language -Open Poedit and select "Create New Translation", then from Shaarli's directory open +Open Poedit and select "Create New Translation", then from Shaarli's directory open `inc/languages//LC_MESSAGES/shaarli.po`. -Then select the language you want to create. +Then select the language you want to create. -Click on `File > Save as...`, and save your file in `/inc/language//LC_MESSAGES/shaarli.po`. -`` here should be the language code respecting the [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-2) +Click on `File > Save as...`, and save your file in `/inc/language//LC_MESSAGES/shaarli.po`. +`` here should be the language code respecting the [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-2) format in lowercase (e.g. `de` for German). Then click on the "Update" button, and you can start to translate every available string. Save when you're done, then you can submit a pull request containing the new `shaarli.po`. -### Theme translations +### Theme translations Theme translation extensions are loaded automatically if they're present. @@ -85,7 +85,7 @@ As a theme developer, all you have to do is to add the `.po` and `.mo` compiled tpl//language//LC_MESSAGES/.po tpl//language//LC_MESSAGES/.mo -Where `` is the ISO 3166-1 alpha-2 language code. +Where `` is the ISO 3166-1 alpha-2 language code. Read the following section "Extend Shaarli's translation" to learn how to generate those files. ### Extend Shaarli's translation @@ -106,7 +106,7 @@ First, create your translation files tree directory: Your `.po` files must be named like your domain. E.g. if your translation domain is `my_theme`, then your file will be `my_theme.po`. -Users have to register your extension in their configuration with the parameter +Users have to register your extension in their configuration with the parameter `translation.extensions.: `. Example: @@ -151,11 +151,11 @@ When you're done, open Poedit and load translation strings from sources: 1. `File > New` 2. Choose your language 3. Save your `PO` file in `/languages//LC_MESSAGES/my_theme.po`. - 4. Go to `Catalog > Properties...` + 4. Go to `Catalog > Properties...` 5. Fill the `Translation Properties` tab 6. Add your source path in the `Sources Paths` tab 7. In the `Sources Keywords` tab uncheck "Also use default keywords" and add the following lines: - + ``` my_theme_t my_theme_t:1,2 diff --git a/index.php b/index.php index 76ad3696..474d9af5 100644 --- a/index.php +++ b/index.php @@ -61,29 +61,31 @@ require_once 'application/FileUtils.php'; require_once 'application/TimeZone.php'; require_once 'application/Utils.php'; -use \Shaarli\ApplicationUtils; -use Shaarli\Bookmark\BookmarkServiceInterface; -use \Shaarli\Bookmark\Exception\BookmarkNotFoundException; +use Shaarli\ApplicationUtils; use Shaarli\Bookmark\Bookmark; -use Shaarli\Bookmark\BookmarkFilter; use Shaarli\Bookmark\BookmarkFileService; -use \Shaarli\Config\ConfigManager; -use \Shaarli\Feed\CachedPage; -use \Shaarli\Feed\FeedBuilder; +use Shaarli\Bookmark\BookmarkFilter; +use Shaarli\Bookmark\BookmarkServiceInterface; +use Shaarli\Bookmark\Exception\BookmarkNotFoundException; +use Shaarli\Config\ConfigManager; +use Shaarli\Container\ContainerBuilder; +use Shaarli\Feed\CachedPage; +use Shaarli\Feed\FeedBuilder; use Shaarli\Formatter\BookmarkMarkdownFormatter; use Shaarli\Formatter\FormatterFactory; -use \Shaarli\History; -use \Shaarli\Languages; -use \Shaarli\Netscape\NetscapeBookmarkUtils; -use \Shaarli\Plugin\PluginManager; -use \Shaarli\Render\PageBuilder; -use \Shaarli\Render\ThemeUtils; -use \Shaarli\Router; -use \Shaarli\Security\LoginManager; -use \Shaarli\Security\SessionManager; -use \Shaarli\Thumbnailer; -use \Shaarli\Updater\Updater; -use \Shaarli\Updater\UpdaterUtils; +use Shaarli\History; +use Shaarli\Languages; +use Shaarli\Netscape\NetscapeBookmarkUtils; +use Shaarli\Plugin\PluginManager; +use Shaarli\Render\PageBuilder; +use Shaarli\Render\ThemeUtils; +use Shaarli\Router; +use Shaarli\Security\LoginManager; +use Shaarli\Security\SessionManager; +use Shaarli\Thumbnailer; +use Shaarli\Updater\Updater; +use Shaarli\Updater\UpdaterUtils; +use Slim\App; // Ensure the PHP version is supported try { @@ -250,7 +252,7 @@ if (isset($_POST['login'])) { // Optional redirect after login: if (isset($_GET['post'])) { - $uri = '?post='. urlencode($_GET['post']); + $uri = './?post='. urlencode($_GET['post']); foreach (array('description', 'source', 'title', 'tags') as $param) { if (!empty($_GET[$param])) { $uri .= '&'.$param.'='.urlencode($_GET[$param]); @@ -261,22 +263,22 @@ if (isset($_POST['login'])) { } if (isset($_GET['edit_link'])) { - header('Location: ?edit_link='. escape($_GET['edit_link'])); + header('Location: ./?edit_link='. escape($_GET['edit_link'])); exit; } if (isset($_POST['returnurl'])) { // Prevent loops over login screen. - if (strpos($_POST['returnurl'], 'do=login') === false) { + if (strpos($_POST['returnurl'], '/login') === false) { header('Location: '. generateLocation($_POST['returnurl'], $_SERVER['HTTP_HOST'])); exit; } } - header('Location: ?'); + header('Location: ./?'); exit; } else { $loginManager->handleFailedLogin($_SERVER); - $redir = '&username='. urlencode($_POST['login']); + $redir = '?username='. urlencode($_POST['login']); if (isset($_GET['post'])) { $redir .= '&post=' . urlencode($_GET['post']); foreach (array('description', 'source', 'title', 'tags') as $param) { @@ -286,7 +288,7 @@ if (isset($_POST['login'])) { } } // Redirect to login screen. - echo ''; + echo ''; exit; } } @@ -594,19 +596,7 @@ function renderPage($conf, $pluginManager, $bookmarkService, $history, $sessionM // -------- Display login form. if ($targetPage == Router::$PAGE_LOGIN) { - if ($conf->get('security.open_shaarli')) { - header('Location: ?'); - exit; - } // No need to login for open Shaarli - if (isset($_GET['username'])) { - $PAGE->assign('username', escape($_GET['username'])); - } - $PAGE->assign('returnurl', (isset($_SERVER['HTTP_REFERER']) ? escape($_SERVER['HTTP_REFERER']):'')); - // add default state of the 'remember me' checkbox - $PAGE->assign('remember_user_default', $conf->get('privacy.remember_user_default')); - $PAGE->assign('user_can_login', $loginManager->canLogin($_SERVER)); - $PAGE->assign('pagetitle', t('Login') .' - '. $conf->get('general.title', 'Shaarli')); - $PAGE->renderPage('loginform'); + header('Location: ./login'); exit; } // -------- User wants to logout. @@ -933,7 +923,7 @@ function renderPage($conf, $pluginManager, $bookmarkService, $history, $sessionM // Show login screen, then redirect to ?post=... if (isset($_GET['post'])) { header( // Redirect to login page, then back to post link. - 'Location: ?do=login&post='.urlencode($_GET['post']). + 'Location: /login?post='.urlencode($_GET['post']). (!empty($_GET['title'])?'&title='.urlencode($_GET['title']):''). (!empty($_GET['description'])?'&description='.urlencode($_GET['description']):''). (!empty($_GET['tags'])?'&tags='.urlencode($_GET['tags']):''). @@ -944,7 +934,7 @@ function renderPage($conf, $pluginManager, $bookmarkService, $history, $sessionM showLinkList($PAGE, $bookmarkService, $conf, $pluginManager, $loginManager); if (isset($_GET['edit_link'])) { - header('Location: ?do=login&edit_link='. escape($_GET['edit_link'])); + header('Location: /login?edit_link='. escape($_GET['edit_link'])); exit; } @@ -1900,7 +1890,7 @@ function install($conf, $sessionManager, $loginManager) echo ''; + .');document.location=\'./login\';'; exit; } @@ -1930,11 +1920,9 @@ if (isset($_SERVER['QUERY_STRING']) && startsWith($_SERVER['QUERY_STRING'], 'do= exit; } -$container = new \Slim\Container(); -$container['conf'] = $conf; -$container['plugins'] = $pluginManager; -$container['history'] = $history; -$app = new \Slim\App($container); +$containerBuilder = new ContainerBuilder($conf, $sessionManager, $loginManager); +$container = $containerBuilder->build(); +$app = new App($container); // REST API routes $app->group('/api/v1', function () { @@ -1953,6 +1941,10 @@ $app->group('/api/v1', function () { $this->get('/history', '\Shaarli\Api\Controllers\HistoryController:getHistory')->setName('getHistory'); })->add('\Shaarli\Api\ApiMiddleware'); +$app->group('', function () { + $this->get('/login', '\Shaarli\Front\Controller\LoginController:index')->setName('login'); +})->add('\Shaarli\Front\ShaarliMiddleware'); + $response = $app->run(true); // Hack to make Slim and Shaarli router work together: diff --git a/tests/UtilsTest.php b/tests/UtilsTest.php index 8225d95a..26d2a6b8 100644 --- a/tests/UtilsTest.php +++ b/tests/UtilsTest.php @@ -203,7 +203,7 @@ class UtilsTest extends PHPUnit\Framework\TestCase public function testGenerateLocationLoop() { $ref = 'http://localhost/?test'; - $this->assertEquals('?', generateLocation($ref, 'localhost', array('test'))); + $this->assertEquals('./?', generateLocation($ref, 'localhost', array('test'))); } /** @@ -212,7 +212,7 @@ class UtilsTest extends PHPUnit\Framework\TestCase public function testGenerateLocationOut() { $ref = 'http://somewebsite.com/?test'; - $this->assertEquals('?', generateLocation($ref, 'localhost')); + $this->assertEquals('./?', generateLocation($ref, 'localhost')); } diff --git a/tests/container/ContainerBuilderTest.php b/tests/container/ContainerBuilderTest.php new file mode 100644 index 00000000..9b97ed6d --- /dev/null +++ b/tests/container/ContainerBuilderTest.php @@ -0,0 +1,49 @@ +conf = new ConfigManager('tests/utils/config/configJson'); + $this->sessionManager = $this->createMock(SessionManager::class); + $this->loginManager = $this->createMock(LoginManager::class); + + $this->containerBuilder = new ContainerBuilder($this->conf, $this->sessionManager, $this->loginManager); + } + + public function testBuildContainer(): void + { + $container = $this->containerBuilder->build(); + + static::assertInstanceOf(ConfigManager::class, $container->conf); + static::assertInstanceOf(SessionManager::class, $container->sessionManager); + static::assertInstanceOf(LoginManager::class, $container->loginManager); + static::assertInstanceOf(History::class, $container->history); + static::assertInstanceOf(BookmarkServiceInterface::class, $container->bookmarkService); + static::assertInstanceOf(PageBuilder::class, $container->pageBuilder); + } +} diff --git a/tests/front/ShaarliMiddlewareTest.php b/tests/front/ShaarliMiddlewareTest.php new file mode 100644 index 00000000..80974f37 --- /dev/null +++ b/tests/front/ShaarliMiddlewareTest.php @@ -0,0 +1,70 @@ +container = $this->createMock(ShaarliContainer::class); + $this->middleware = new ShaarliMiddleware($this->container); + } + + public function testMiddlewareExecution(): void + { + $request = $this->createMock(Request::class); + $response = new Response(); + $controller = function (Request $request, Response $response): Response { + return $response->withStatus(418); // I'm a tea pot + }; + + /** @var Response $result */ + $result = $this->middleware->__invoke($request, $response, $controller); + + static::assertInstanceOf(Response::class, $result); + static::assertSame(418, $result->getStatusCode()); + } + + public function testMiddlewareExecutionWithException(): void + { + $request = $this->createMock(Request::class); + $response = new Response(); + $controller = function (): void { + $exception = new LoginBannedException(); + + throw new $exception; + }; + + $pageBuilder = $this->createMock(PageBuilder::class); + $pageBuilder->method('render')->willReturnCallback(function (string $message): string { + return $message; + }); + $this->container->pageBuilder = $pageBuilder; + + $conf = $this->createMock(ConfigManager::class); + $this->container->conf = $conf; + + /** @var Response $result */ + $result = $this->middleware->__invoke($request, $response, $controller); + + static::assertInstanceOf(Response::class, $result); + static::assertSame(401, $result->getStatusCode()); + static::assertContains('error', (string) $result->getBody()); + } +} diff --git a/tests/front/controller/LoginControllerTest.php b/tests/front/controller/LoginControllerTest.php new file mode 100644 index 00000000..8cf8ece7 --- /dev/null +++ b/tests/front/controller/LoginControllerTest.php @@ -0,0 +1,178 @@ +container = $this->createMock(ShaarliContainer::class); + $this->controller = new LoginController($this->container); + } + + public function testValidControllerInvoke(): void + { + $this->createValidContainerMockSet(); + + $request = $this->createMock(Request::class); + $request->expects(static::once())->method('getServerParam')->willReturn('> referer'); + $response = new Response(); + + $assignedVariables = []; + $this->container->pageBuilder + ->method('assign') + ->willReturnCallback(function ($key, $value) use (&$assignedVariables) { + $assignedVariables[$key] = $value; + + return $this; + }) + ; + + $result = $this->controller->index($request, $response); + + static::assertInstanceOf(Response::class, $result); + static::assertSame(200, $result->getStatusCode()); + static::assertSame('loginform', (string) $result->getBody()); + + static::assertSame('> referer', $assignedVariables['returnurl']); + static::assertSame(true, $assignedVariables['remember_user_default']); + static::assertSame('Login - Shaarli', $assignedVariables['pagetitle']); + } + + public function testValidControllerInvokeWithUserName(): void + { + $this->createValidContainerMockSet(); + + $request = $this->createMock(Request::class); + $request->expects(static::once())->method('getServerParam')->willReturn('> referer'); + $request->expects(static::exactly(2))->method('getParam')->willReturn('myUser>'); + $response = new Response(); + + $assignedVariables = []; + $this->container->pageBuilder + ->method('assign') + ->willReturnCallback(function ($key, $value) use (&$assignedVariables) { + $assignedVariables[$key] = $value; + + return $this; + }) + ; + + $result = $this->controller->index($request, $response); + + static::assertInstanceOf(Response::class, $result); + static::assertSame(200, $result->getStatusCode()); + static::assertSame('loginform', (string) $result->getBody()); + + static::assertSame('myUser>', $assignedVariables['username']); + static::assertSame('> referer', $assignedVariables['returnurl']); + static::assertSame(true, $assignedVariables['remember_user_default']); + static::assertSame('Login - Shaarli', $assignedVariables['pagetitle']); + } + + public function testLoginControllerWhileLoggedIn(): void + { + $request = $this->createMock(Request::class); + $response = new Response(); + + $loginManager = $this->createMock(LoginManager::class); + $loginManager->expects(static::once())->method('isLoggedIn')->willReturn(true); + $this->container->loginManager = $loginManager; + + $result = $this->controller->index($request, $response); + + static::assertInstanceOf(Response::class, $result); + static::assertSame(302, $result->getStatusCode()); + static::assertSame(['./'], $result->getHeader('Location')); + } + + public function testLoginControllerOpenShaarli(): void + { + $this->createValidContainerMockSet(); + + $request = $this->createMock(Request::class); + $response = new Response(); + + $conf = $this->createMock(ConfigManager::class); + $conf->method('get')->willReturnCallback(function (string $parameter, $default) { + if ($parameter === 'security.open_shaarli') { + return true; + } + return $default; + }); + $this->container->conf = $conf; + + $result = $this->controller->index($request, $response); + + static::assertInstanceOf(Response::class, $result); + static::assertSame(302, $result->getStatusCode()); + static::assertSame(['./'], $result->getHeader('Location')); + } + + public function testLoginControllerWhileBanned(): void + { + $this->createValidContainerMockSet(); + + $request = $this->createMock(Request::class); + $response = new Response(); + + $loginManager = $this->createMock(LoginManager::class); + $loginManager->method('isLoggedIn')->willReturn(false); + $loginManager->method('canLogin')->willReturn(false); + $this->container->loginManager = $loginManager; + + $this->expectException(LoginBannedException::class); + + $this->controller->index($request, $response); + } + + protected function createValidContainerMockSet(): void + { + // User logged out + $loginManager = $this->createMock(LoginManager::class); + $loginManager->method('isLoggedIn')->willReturn(false); + $loginManager->method('canLogin')->willReturn(true); + $this->container->loginManager = $loginManager; + + // Config + $conf = $this->createMock(ConfigManager::class); + $conf->method('get')->willReturnCallback(function (string $parameter, $default) { + return $default; + }); + $this->container->conf = $conf; + + // PageBuilder + $pageBuilder = $this->createMock(PageBuilder::class); + $pageBuilder + ->method('render') + ->willReturnCallback(function (string $template): string { + return $template; + }) + ; + $this->container->pageBuilder = $pageBuilder; + + $pluginManager = $this->createMock(PluginManager::class); + $this->container->pluginManager = $pluginManager; + $bookmarkService = $this->createMock(BookmarkServiceInterface::class); + $this->container->bookmarkService = $bookmarkService; + } +} diff --git a/tests/front/controller/ShaarliControllerTest.php b/tests/front/controller/ShaarliControllerTest.php new file mode 100644 index 00000000..6fa3feb9 --- /dev/null +++ b/tests/front/controller/ShaarliControllerTest.php @@ -0,0 +1,116 @@ +container = $this->createMock(ShaarliContainer::class); + $this->controller = new class($this->container) extends ShaarliController + { + public function assignView(string $key, $value): ShaarliController + { + return parent::assignView($key, $value); + } + + public function render(string $template): string + { + return parent::render($template); + } + }; + $this->assignedValues = []; + } + + public function testAssignView(): void + { + $this->createValidContainerMockSet(); + + $self = $this->controller->assignView('variableName', 'variableValue'); + + static::assertInstanceOf(ShaarliController::class, $self); + static::assertSame('variableValue', $this->assignedValues['variableName']); + } + + public function testRender(): void + { + $this->createValidContainerMockSet(); + + $render = $this->controller->render('templateName'); + + static::assertSame('templateName', $render); + + static::assertSame(10, $this->assignedValues['linkcount']); + static::assertSame(5, $this->assignedValues['privateLinkcount']); + static::assertSame(['error'], $this->assignedValues['plugin_errors']); + + static::assertSame('templateName', $this->assignedValues['plugins_includes']['render_includes']['target']); + static::assertTrue($this->assignedValues['plugins_includes']['render_includes']['loggedin']); + static::assertSame('templateName', $this->assignedValues['plugins_header']['render_header']['target']); + static::assertTrue($this->assignedValues['plugins_header']['render_header']['loggedin']); + static::assertSame('templateName', $this->assignedValues['plugins_footer']['render_footer']['target']); + static::assertTrue($this->assignedValues['plugins_footer']['render_footer']['loggedin']); + } + + protected function createValidContainerMockSet(): void + { + $pageBuilder = $this->createMock(PageBuilder::class); + $pageBuilder + ->method('assign') + ->willReturnCallback(function (string $key, $value): void { + $this->assignedValues[$key] = $value; + }); + $pageBuilder + ->method('render') + ->willReturnCallback(function (string $template): string { + return $template; + }); + $this->container->pageBuilder = $pageBuilder; + + $bookmarkService = $this->createMock(BookmarkServiceInterface::class); + $bookmarkService + ->method('count') + ->willReturnCallback(function (string $visibility): int { + return $visibility === BookmarkFilter::$PRIVATE ? 5 : 10; + }); + $this->container->bookmarkService = $bookmarkService; + + $pluginManager = $this->createMock(PluginManager::class); + $pluginManager + ->method('executeHooks') + ->willReturnCallback(function (string $hook, array &$data, array $params): array { + return $data[$hook] = $params; + }); + $pluginManager->method('getErrors')->willReturn(['error']); + $this->container->pluginManager = $pluginManager; + + $loginManager = $this->createMock(LoginManager::class); + $loginManager->method('isLoggedIn')->willReturn(true); + $this->container->loginManager = $loginManager; + } +} diff --git a/tpl/default/404.html b/tpl/default/404.html index 472566a6..09737b4b 100644 --- a/tpl/default/404.html +++ b/tpl/default/404.html @@ -6,7 +6,7 @@