From 818b3193ffabec57501e3bdfa997206e3c0671ef Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Sat, 13 Jun 2020 11:22:14 +0200 Subject: Explicitly define base and asset path in templates With the new routes, all pages are not all at the same folder level anymore (e.g. /shaare and /shaare/123), so we can't just use './' everywhere. The most consistent way to handle this is to prefix all path with the proper variable, and handle the actual path in controllers. --- application/container/ShaarliContainer.php | 1 + application/front/ShaarliMiddleware.php | 2 + .../visitor/ShaarliVisitorController.php | 15 +- application/render/PageBuilder.php | 4 + assets/common/js/thumbnails-update.js | 8 +- assets/default/js/base.js | 27 ++- tests/front/ShaarliMiddlewareTest.php | 15 ++ .../admin/PostBookmarkControllerTest.php | 19 -- .../admin/SessionFilterControllerTest.php | 60 +----- .../visitor/FrontControllerMockHelper.php | 2 + .../visitor/ShaarliPublicControllerTest.php | 221 --------------------- .../visitor/ShaarliVisitorControllerTest.php | 217 ++++++++++++++++++++ tpl/default/404.html | 2 +- tpl/default/addlink.html | 2 +- tpl/default/changepassword.html | 2 +- tpl/default/changetag.html | 4 +- tpl/default/configure.html | 6 +- tpl/default/daily.html | 10 +- tpl/default/editlink.html | 4 +- tpl/default/error.html | 2 +- tpl/default/export.html | 2 +- tpl/default/import.html | 2 +- tpl/default/includes.html | 10 +- tpl/default/install.html | 2 +- tpl/default/linklist.html | 18 +- tpl/default/linklist.paging.html | 14 +- tpl/default/page.footer.html | 5 +- tpl/default/page.header.html | 22 +- tpl/default/picwall.html | 4 +- tpl/default/pluginsadmin.html | 6 +- tpl/default/tag.cloud.html | 6 +- tpl/default/tag.list.html | 8 +- tpl/default/tag.sort.html | 6 +- tpl/default/thumbnails.html | 2 +- tpl/default/tools.html | 14 +- tpl/vintage/404.html | 2 +- tpl/vintage/addlink.html | 2 +- tpl/vintage/configure.html | 4 +- tpl/vintage/daily.html | 20 +- tpl/vintage/editlink.html | 2 +- tpl/vintage/error.html | 2 +- tpl/vintage/import.html | 2 +- tpl/vintage/includes.html | 4 +- tpl/vintage/linklist.html | 16 +- tpl/vintage/linklist.paging.html | 14 +- tpl/vintage/page.footer.html | 2 +- tpl/vintage/page.header.html | 18 +- tpl/vintage/picwall.html | 2 +- tpl/vintage/pluginsadmin.html | 4 +- tpl/vintage/tag.cloud.html | 4 +- tpl/vintage/thumbnails.html | 3 +- tpl/vintage/tools.html | 12 +- 52 files changed, 413 insertions(+), 444 deletions(-) delete mode 100644 tests/front/controller/visitor/ShaarliPublicControllerTest.php create mode 100644 tests/front/controller/visitor/ShaarliVisitorControllerTest.php diff --git a/application/container/ShaarliContainer.php b/application/container/ShaarliContainer.php index fec398d0..a95393cd 100644 --- a/application/container/ShaarliContainer.php +++ b/application/container/ShaarliContainer.php @@ -22,6 +22,7 @@ use Slim\Container; * Extension of Slim container to document the injected objects. * * @property mixed[] $environment $_SERVER automatically injected by Slim + * @property string $basePath Shaarli's instance base path (e.g. `/shaarli/`) * @property ConfigManager $conf * @property SessionManager $sessionManager * @property LoginManager $loginManager diff --git a/application/front/ShaarliMiddleware.php b/application/front/ShaarliMiddleware.php index f8992e0b..47aa61bb 100644 --- a/application/front/ShaarliMiddleware.php +++ b/application/front/ShaarliMiddleware.php @@ -39,6 +39,8 @@ class ShaarliMiddleware public function __invoke(Request $request, Response $response, callable $next) { try { + $this->container->basePath = rtrim($request->getUri()->getBasePath(), '/'); + $response = $next($request, $response); } catch (ShaarliFrontException $e) { $this->container->pageBuilder->assign('message', $e->getMessage()); diff --git a/application/front/controller/visitor/ShaarliVisitorController.php b/application/front/controller/visitor/ShaarliVisitorController.php index 98423d90..b90b1e8f 100644 --- a/application/front/controller/visitor/ShaarliVisitorController.php +++ b/application/front/controller/visitor/ShaarliVisitorController.php @@ -60,6 +60,19 @@ abstract class ShaarliVisitorController $this->assignView('privateLinkcount', $this->container->bookmarkService->count(BookmarkFilter::$PRIVATE)); $this->assignView('plugin_errors', $this->container->pluginManager->getErrors()); + /* + * Define base path (if Shaarli is installed in a domain's subfolder, e.g. `/shaarli`) + * and the asset path (subfolder/tpl/default for default theme). + * These MUST be used to create an internal link or to include an asset in templates. + */ + $this->assignView('base_path', $this->container->basePath); + $this->assignView( + 'asset_path', + $this->container->basePath . '/' . + rtrim($this->container->conf->get('resource.raintpl_tpl', 'tpl'), '/') . '/' . + $this->container->conf->get('resource.theme', 'default') + ); + $this->executeDefaultHooks($template); return $this->container->pageBuilder->render($template); @@ -105,7 +118,7 @@ abstract class ShaarliVisitorController array $clearParams = [], string $anchor = null ): Response { - $defaultPath = rtrim($request->getUri()->getBasePath(), '/') . '/'; + $defaultPath = $this->container->basePath . '/'; $referer = $this->container->environment['HTTP_REFERER'] ?? null; if (null !== $referer) { diff --git a/application/render/PageBuilder.php b/application/render/PageBuilder.php index d90ed58b..2779eb90 100644 --- a/application/render/PageBuilder.php +++ b/application/render/PageBuilder.php @@ -149,6 +149,10 @@ class PageBuilder */ protected function finalize(): void { + //FIXME - DEV _ REMOVE ME + $this->assign('base_path', '/Shaarli'); + $this->assign('asset_path', '/Shaarli/tpl/default'); + // TODO: use the SessionManager $messageKeys = [ SessionManager::KEY_SUCCESS_MESSAGES, diff --git a/assets/common/js/thumbnails-update.js b/assets/common/js/thumbnails-update.js index 060a730e..35608169 100644 --- a/assets/common/js/thumbnails-update.js +++ b/assets/common/js/thumbnails-update.js @@ -10,13 +10,14 @@ * It contains a recursive call to retrieve the thumb of the next link when it succeed. * It also update the progress bar and other visual feedback elements. * + * @param {string} basePath Shaarli subfolder for XHR requests * @param {array} ids List of LinkID to update * @param {int} i Current index in ids * @param {object} elements List of DOM element to avoid retrieving them at each iteration */ -function updateThumb(ids, i, elements) { +function updateThumb(basePath, ids, i, elements) { const xhr = new XMLHttpRequest(); - xhr.open('POST', './?do=ajax_thumb_update'); + xhr.open('POST', `${basePath}/?do=ajax_thumb_update`); xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); xhr.responseType = 'json'; xhr.onload = () => { @@ -40,6 +41,7 @@ function updateThumb(ids, i, elements) { } (() => { + const basePath = document.querySelector('input[name="js_base_path"]').value; const ids = document.getElementsByName('ids')[0].value.split(','); const elements = { progressBar: document.querySelector('.progressbar > div'), @@ -47,5 +49,5 @@ function updateThumb(ids, i, elements) { thumbnail: document.querySelector('.thumbnail-placeholder'), title: document.querySelector('.thumbnail-link-title'), }; - updateThumb(ids, 0, elements); + updateThumb(basePath, ids, 0, elements); })(); diff --git a/assets/default/js/base.js b/assets/default/js/base.js index 8cc7eed5..b428a420 100644 --- a/assets/default/js/base.js +++ b/assets/default/js/base.js @@ -25,9 +25,9 @@ function findParent(element, tagName, attributes) { /** * Ajax request to refresh the CSRF token. */ -function refreshToken() { +function refreshToken(basePath) { const xhr = new XMLHttpRequest(); - xhr.open('GET', './?do=token'); + xhr.open('GET', `${basePath}/?do=token`); xhr.onload = () => { const token = document.getElementById('token'); token.setAttribute('value', xhr.responseText); @@ -215,6 +215,8 @@ function init(description) { } (() => { + const basePath = document.querySelector('input[name="js_base_path"]').value; + /** * Handle responsive menu. * Source: http://purecss.io/layouts/tucked-menu-vertical/ @@ -461,7 +463,7 @@ function init(description) { }); if (window.confirm(message)) { - window.location = `?delete_link&lf_linkdate=${ids.join('+')}&token=${token.value}`; + window.location = `${basePath}/?delete_link&lf_linkdate=${ids.join('+')}&token=${token.value}`; } }); } @@ -483,7 +485,8 @@ function init(description) { }); const ids = links.map(item => item.id); - window.location = `?change_visibility&token=${token.value}&newVisibility=${visibility}&ids=${ids.join('+')}`; + window.location = + `${basePath}/?change_visibility&token=${token.value}&newVisibility=${visibility}&ids=${ids.join('+')}`; }); }); } @@ -546,7 +549,7 @@ function init(description) { const refreshedToken = document.getElementById('token').value; const fromtag = block.getAttribute('data-tag'); const xhr = new XMLHttpRequest(); - xhr.open('POST', './manage-tags'); + xhr.open('POST', `${basePath}/manage-tags`); xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); xhr.onload = () => { if (xhr.status !== 200) { @@ -558,8 +561,12 @@ function init(description) { input.setAttribute('value', totag); findParent(input, 'div', { class: 'rename-tag-form' }).style.display = 'none'; block.querySelector('a.tag-link').innerHTML = htmlEntities(totag); - block.querySelector('a.tag-link').setAttribute('href', `./?searchtags=${encodeURIComponent(totag)}`); - block.querySelector('a.rename-tag').setAttribute('href', `./manage-tags?fromtag=${encodeURIComponent(totag)}`); + block + .querySelector('a.tag-link') + .setAttribute('href', `${basePath}/?searchtags=${encodeURIComponent(totag)}`); + block + .querySelector('a.rename-tag') + .setAttribute('href', `${basePath}/manage-tags?fromtag=${encodeURIComponent(totag)}`); // Refresh awesomplete values existingTags = existingTags.map(tag => (tag === fromtag ? totag : tag)); @@ -567,7 +574,7 @@ function init(description) { } }; xhr.send(`renametag=1&fromtag=${encodeURIComponent(fromtag)}&totag=${encodeURIComponent(totag)}&token=${refreshedToken}`); - refreshToken(); + refreshToken(basePath); }); }); @@ -593,13 +600,13 @@ function init(description) { if (confirm(`Are you sure you want to delete the tag "${tag}"?`)) { const xhr = new XMLHttpRequest(); - xhr.open('POST', './manage-tags'); + xhr.open('POST', `${basePath}/manage-tags`); xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); xhr.onload = () => { block.remove(); }; xhr.send(encodeURI(`deletetag=1&fromtag=${tag}&token=${refreshedToken}`)); - refreshToken(); + refreshToken(basePath); existingTags = existingTags.filter(tagItem => tagItem !== tag); awesomepletes = updateAwesompleteList('.rename-tag-input', existingTags, awesomepletes); diff --git a/tests/front/ShaarliMiddlewareTest.php b/tests/front/ShaarliMiddlewareTest.php index 80974f37..57be1002 100644 --- a/tests/front/ShaarliMiddlewareTest.php +++ b/tests/front/ShaarliMiddlewareTest.php @@ -11,6 +11,7 @@ use Shaarli\Front\Exception\LoginBannedException; use Shaarli\Render\PageBuilder; use Slim\Http\Request; use Slim\Http\Response; +use Slim\Http\Uri; class ShaarliMiddlewareTest extends TestCase { @@ -29,6 +30,13 @@ class ShaarliMiddlewareTest extends TestCase public function testMiddlewareExecution(): void { $request = $this->createMock(Request::class); + $request->method('getUri')->willReturnCallback(function (): Uri { + $uri = $this->createMock(Uri::class); + $uri->method('getBasePath')->willReturn('/subfolder'); + + return $uri; + }); + $response = new Response(); $controller = function (Request $request, Response $response): Response { return $response->withStatus(418); // I'm a tea pot @@ -44,6 +52,13 @@ class ShaarliMiddlewareTest extends TestCase public function testMiddlewareExecutionWithException(): void { $request = $this->createMock(Request::class); + $request->method('getUri')->willReturnCallback(function (): Uri { + $uri = $this->createMock(Uri::class); + $uri->method('getBasePath')->willReturn('/subfolder'); + + return $uri; + }); + $response = new Response(); $controller = function (): void { $exception = new LoginBannedException(); diff --git a/tests/front/controller/admin/PostBookmarkControllerTest.php b/tests/front/controller/admin/PostBookmarkControllerTest.php index f00a15c9..69673bd2 100644 --- a/tests/front/controller/admin/PostBookmarkControllerTest.php +++ b/tests/front/controller/admin/PostBookmarkControllerTest.php @@ -13,7 +13,6 @@ use Shaarli\Security\SessionManager; use Shaarli\Thumbnailer; use Slim\Http\Request; use Slim\Http\Response; -use Slim\Http\Uri; class PostBookmarkControllerTest extends TestCase { @@ -406,12 +405,6 @@ class PostBookmarkControllerTest extends TestCase return $parameters[$key] ?? null; }) ; - $request->method('getUri')->willReturnCallback(function (): Uri { - $uri = $this->createMock(Uri::class); - $uri->method('getBasePath')->willReturn('/subfolder'); - - return $uri; - }); $response = new Response(); $checkBookmark = function (Bookmark $bookmark) use ($parameters) { @@ -493,12 +486,6 @@ class PostBookmarkControllerTest extends TestCase return $parameters[$key] ?? null; }) ; - $request->method('getUri')->willReturnCallback(function (): Uri { - $uri = $this->createMock(Uri::class); - $uri->method('getBasePath')->willReturn('/subfolder'); - - return $uri; - }); $response = new Response(); $checkBookmark = function (Bookmark $bookmark) use ($parameters, $id) { @@ -575,12 +562,6 @@ class PostBookmarkControllerTest extends TestCase return $parameters[$key] ?? null; }) ; - $request->method('getUri')->willReturnCallback(function (): Uri { - $uri = $this->createMock(Uri::class); - $uri->method('getBasePath')->willReturn('/subfolder'); - - return $uri; - }); $response = new Response(); $this->container->conf = $this->createMock(ConfigManager::class); diff --git a/tests/front/controller/admin/SessionFilterControllerTest.php b/tests/front/controller/admin/SessionFilterControllerTest.php index 096963cf..ea07edee 100644 --- a/tests/front/controller/admin/SessionFilterControllerTest.php +++ b/tests/front/controller/admin/SessionFilterControllerTest.php @@ -9,7 +9,6 @@ use Shaarli\Security\LoginManager; use Shaarli\Security\SessionManager; use Slim\Http\Request; use Slim\Http\Response; -use Slim\Http\Uri; class SessionFilterControllerTest extends TestCase { @@ -33,12 +32,6 @@ class SessionFilterControllerTest extends TestCase $this->container->environment = ['HTTP_REFERER' => 'http://shaarli/subfolder/controller/?searchtag=abc']; $request = $this->createMock(Request::class); - $request->method('getUri')->willReturnCallback(function (): Uri { - $uri = $this->createMock(Uri::class); - $uri->method('getBasePath')->willReturn('/subfolder'); - - return $uri; - }); $request->method('getParam')->with('nb')->willReturn('8'); $response = new Response(); @@ -61,12 +54,6 @@ class SessionFilterControllerTest extends TestCase public function testLinksPerPageNotValid(): void { $request = $this->createMock(Request::class); - $request->method('getUri')->willReturnCallback(function (): Uri { - $uri = $this->createMock(Uri::class); - $uri->method('getBasePath')->willReturn('/subfolder'); - - return $uri; - }); $request->method('getParam')->with('nb')->willReturn('test'); $response = new Response(); @@ -80,7 +67,7 @@ class SessionFilterControllerTest extends TestCase static::assertInstanceOf(Response::class, $result); static::assertSame(302, $result->getStatusCode()); - static::assertSame(['/subfolder'], $result->getHeader('location')); + static::assertSame(['/subfolder/'], $result->getHeader('location')); } /** @@ -100,12 +87,6 @@ class SessionFilterControllerTest extends TestCase ; $request = $this->createMock(Request::class); - $request->method('getUri')->willReturnCallback(function (): Uri { - $uri = $this->createMock(Uri::class); - $uri->method('getBasePath')->willReturn('/subfolder'); - - return $uri; - }); $response = new Response(); $result = $this->controller->visibility($request, $response, $arg); @@ -141,12 +122,6 @@ class SessionFilterControllerTest extends TestCase ; $request = $this->createMock(Request::class); - $request->method('getUri')->willReturnCallback(function (): Uri { - $uri = $this->createMock(Uri::class); - $uri->method('getBasePath')->willReturn('/subfolder'); - - return $uri; - }); $response = new Response(); $result = $this->controller->visibility($request, $response, $arg); @@ -176,19 +151,13 @@ class SessionFilterControllerTest extends TestCase ; $request = $this->createMock(Request::class); - $request->method('getUri')->willReturnCallback(function (): Uri { - $uri = $this->createMock(Uri::class); - $uri->method('getBasePath')->willReturn('/subfolder'); - - return $uri; - }); $response = new Response(); $result = $this->controller->visibility($request, $response, $arg); static::assertInstanceOf(Response::class, $result); static::assertSame(302, $result->getStatusCode()); - static::assertSame(['/subfolder'], $result->getHeader('location')); + static::assertSame(['/subfolder/'], $result->getHeader('location')); } /** @@ -212,12 +181,6 @@ class SessionFilterControllerTest extends TestCase ; $request = $this->createMock(Request::class); - $request->method('getUri')->willReturnCallback(function (): Uri { - $uri = $this->createMock(Uri::class); - $uri->method('getBasePath')->willReturn('/subfolder'); - - return $uri; - }); $response = new Response(); $result = $this->controller->visibility($request, $response, $arg); @@ -249,12 +212,6 @@ class SessionFilterControllerTest extends TestCase ; $request = $this->createMock(Request::class); - $request->method('getUri')->willReturnCallback(function (): Uri { - $uri = $this->createMock(Uri::class); - $uri->method('getBasePath')->willReturn('/subfolder'); - - return $uri; - }); $response = new Response(); $result = $this->controller->visibility($request, $response, $arg); @@ -272,12 +229,6 @@ class SessionFilterControllerTest extends TestCase $this->container->environment = ['HTTP_REFERER' => 'http://shaarli/subfolder/controller/?searchtag=abc']; $request = $this->createMock(Request::class); - $request->method('getUri')->willReturnCallback(function (): Uri { - $uri = $this->createMock(Uri::class); - $uri->method('getBasePath')->willReturn('/subfolder'); - - return $uri; - }); $response = new Response(); $this->container->sessionManager @@ -301,13 +252,6 @@ class SessionFilterControllerTest extends TestCase $this->container->environment = ['HTTP_REFERER' => 'http://shaarli/subfolder/controller/?searchtag=abc']; $request = $this->createMock(Request::class); - $request->method('getUri')->willReturnCallback(function (): Uri { - $uri = $this->createMock(Uri::class); - $uri->method('getBasePath')->willReturn('/subfolder'); - - return $uri; - }); - $response = new Response(); $this->container->sessionManager diff --git a/tests/front/controller/visitor/FrontControllerMockHelper.php b/tests/front/controller/visitor/FrontControllerMockHelper.php index fecd0c82..7f560662 100644 --- a/tests/front/controller/visitor/FrontControllerMockHelper.php +++ b/tests/front/controller/visitor/FrontControllerMockHelper.php @@ -81,6 +81,8 @@ trait FrontControllerMockHelper 'SERVER_PORT' => '80', 'REQUEST_URI' => '/daily-rss', ]; + + $this->container->basePath = '/subfolder'; } /** diff --git a/tests/front/controller/visitor/ShaarliPublicControllerTest.php b/tests/front/controller/visitor/ShaarliPublicControllerTest.php deleted file mode 100644 index 899b280b..00000000 --- a/tests/front/controller/visitor/ShaarliPublicControllerTest.php +++ /dev/null @@ -1,221 +0,0 @@ -createContainer(); - - $this->controller = new class($this->container) extends ShaarliVisitorController - { - public function assignView(string $key, $value): ShaarliVisitorController - { - return parent::assignView($key, $value); - } - - public function render(string $template): string - { - return parent::render($template); - } - - public function redirectFromReferer( - Request $request, - Response $response, - array $loopTerms = [], - array $clearParams = [] - ): Response { - return parent::redirectFromReferer($request, $response, $loopTerms, $clearParams); - } - }; - $this->assignedValues = []; - - $this->request = $this->createMock(Request::class); - $this->request->method('getUri')->willReturnCallback(function (): Uri { - $uri = $this->createMock(Uri::class); - $uri->method('getBasePath')->willReturn('/subfolder'); - - return $uri; - }); - } - - public function testAssignView(): void - { - $this->assignTemplateVars($this->assignedValues); - - $self = $this->controller->assignView('variableName', 'variableValue'); - - static::assertInstanceOf(ShaarliVisitorController::class, $self); - static::assertSame('variableValue', $this->assignedValues['variableName']); - } - - public function testRender(): void - { - $this->assignTemplateVars($this->assignedValues); - - $this->container->bookmarkService - ->method('count') - ->willReturnCallback(function (string $visibility): int { - return $visibility === BookmarkFilter::$PRIVATE ? 5 : 10; - }) - ; - - $this->container->pluginManager - ->method('executeHooks') - ->willReturnCallback(function (string $hook, array &$data, array $params): array { - return $data[$hook] = $params; - }); - $this->container->pluginManager->method('getErrors')->willReturn(['error']); - - $this->container->loginManager->method('isLoggedIn')->willReturn(true); - - $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']); - } - - /** - * Test redirectFromReferer() - Default behaviour - */ - public function testRedirectFromRefererDefault(): void - { - $this->container->environment['HTTP_REFERER'] = 'http://shaarli.tld/subfolder/controller?query=param&other=2'; - - $response = new Response(); - - $result = $this->controller->redirectFromReferer($this->request, $response); - - static::assertSame(302, $result->getStatusCode()); - static::assertSame(['/subfolder/controller?query=param&other=2'], $result->getHeader('location')); - } - - /** - * Test redirectFromReferer() - With a loop term not matched in the referer - */ - public function testRedirectFromRefererWithUnmatchedLoopTerm(): void - { - $this->container->environment['HTTP_REFERER'] = 'http://shaarli.tld/subfolder/controller?query=param&other=2'; - - $response = new Response(); - - $result = $this->controller->redirectFromReferer($this->request, $response, ['nope']); - - static::assertSame(302, $result->getStatusCode()); - static::assertSame(['/subfolder/controller?query=param&other=2'], $result->getHeader('location')); - } - - /** - * Test redirectFromReferer() - With a loop term matching the referer in its path -> redirect to default - */ - public function testRedirectFromRefererWithMatchingLoopTermInPath(): void - { - $this->container->environment['HTTP_REFERER'] = 'http://shaarli.tld/subfolder/controller?query=param&other=2'; - - $response = new Response(); - - $result = $this->controller->redirectFromReferer($this->request, $response, ['nope', 'controller']); - - static::assertSame(302, $result->getStatusCode()); - static::assertSame(['/subfolder'], $result->getHeader('location')); - } - - /** - * Test redirectFromReferer() - With a loop term matching the referer in its query parameters -> redirect to default - */ - public function testRedirectFromRefererWithMatchingLoopTermInQueryParam(): void - { - $this->container->environment['HTTP_REFERER'] = 'http://shaarli.tld/subfolder/controller?query=param&other=2'; - - $response = new Response(); - - $result = $this->controller->redirectFromReferer($this->request, $response, ['nope', 'other']); - - static::assertSame(302, $result->getStatusCode()); - static::assertSame(['/subfolder'], $result->getHeader('location')); - } - - /** - * Test redirectFromReferer() - With a loop term matching the referer in its query value - * -> we do not block redirection for query parameter values. - */ - public function testRedirectFromRefererWithMatchingLoopTermInQueryValue(): void - { - $this->container->environment['HTTP_REFERER'] = 'http://shaarli.tld/subfolder/controller?query=param&other=2'; - - $response = new Response(); - - $result = $this->controller->redirectFromReferer($this->request, $response, ['nope', 'param']); - - static::assertSame(302, $result->getStatusCode()); - static::assertSame(['/subfolder/controller?query=param&other=2'], $result->getHeader('location')); - } - - /** - * Test redirectFromReferer() - With a loop term matching the referer in its domain name - * -> we do not block redirection for shaarli's hosts - */ - public function testRedirectFromRefererWithLoopTermInDomain(): void - { - $this->container->environment['HTTP_REFERER'] = 'http://shaarli.tld/subfolder/controller?query=param&other=2'; - - $response = new Response(); - - $result = $this->controller->redirectFromReferer($this->request, $response, ['shaarli']); - - static::assertSame(302, $result->getStatusCode()); - static::assertSame(['/subfolder/controller?query=param&other=2'], $result->getHeader('location')); - } - - /** - * Test redirectFromReferer() - With a loop term matching a query parameter AND clear this query param - * -> the param should be cleared before checking if it matches the redir loop terms - */ - public function testRedirectFromRefererWithMatchingClearedParam(): void - { - $this->container->environment['HTTP_REFERER'] = 'http://shaarli.tld/subfolder/controller?query=param&other=2'; - - $response = new Response(); - - $result = $this->controller->redirectFromReferer($this->request, $response, ['query'], ['query']); - - static::assertSame(302, $result->getStatusCode()); - static::assertSame(['/subfolder/controller?other=2'], $result->getHeader('location')); - } -} diff --git a/tests/front/controller/visitor/ShaarliVisitorControllerTest.php b/tests/front/controller/visitor/ShaarliVisitorControllerTest.php new file mode 100644 index 00000000..83d08358 --- /dev/null +++ b/tests/front/controller/visitor/ShaarliVisitorControllerTest.php @@ -0,0 +1,217 @@ +createContainer(); + + $this->controller = new class($this->container) extends ShaarliVisitorController + { + public function assignView(string $key, $value): ShaarliVisitorController + { + return parent::assignView($key, $value); + } + + public function render(string $template): string + { + return parent::render($template); + } + + public function redirectFromReferer( + Request $request, + Response $response, + array $loopTerms = [], + array $clearParams = [], + string $anchor = null + ): Response { + return parent::redirectFromReferer($request, $response, $loopTerms, $clearParams, $anchor); + } + }; + $this->assignedValues = []; + + $this->request = $this->createMock(Request::class); + } + + public function testAssignView(): void + { + $this->assignTemplateVars($this->assignedValues); + + $self = $this->controller->assignView('variableName', 'variableValue'); + + static::assertInstanceOf(ShaarliVisitorController::class, $self); + static::assertSame('variableValue', $this->assignedValues['variableName']); + } + + public function testRender(): void + { + $this->assignTemplateVars($this->assignedValues); + + $this->container->bookmarkService + ->method('count') + ->willReturnCallback(function (string $visibility): int { + return $visibility === BookmarkFilter::$PRIVATE ? 5 : 10; + }) + ; + + $this->container->pluginManager + ->method('executeHooks') + ->willReturnCallback(function (string $hook, array &$data, array $params): array { + return $data[$hook] = $params; + }); + $this->container->pluginManager->method('getErrors')->willReturn(['error']); + + $this->container->loginManager->method('isLoggedIn')->willReturn(true); + + $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('/subfolder', $this->assignedValues['base_path']); + static::assertSame('/subfolder/tpl/default', $this->assignedValues['asset_path']); + + 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']); + } + + /** + * Test redirectFromReferer() - Default behaviour + */ + public function testRedirectFromRefererDefault(): void + { + $this->container->environment['HTTP_REFERER'] = 'http://shaarli.tld/subfolder/controller?query=param&other=2'; + + $response = new Response(); + + $result = $this->controller->redirectFromReferer($this->request, $response); + + static::assertSame(302, $result->getStatusCode()); + static::assertSame(['/subfolder/controller?query=param&other=2'], $result->getHeader('location')); + } + + /** + * Test redirectFromReferer() - With a loop term not matched in the referer + */ + public function testRedirectFromRefererWithUnmatchedLoopTerm(): void + { + $this->container->environment['HTTP_REFERER'] = 'http://shaarli.tld/subfolder/controller?query=param&other=2'; + + $response = new Response(); + + $result = $this->controller->redirectFromReferer($this->request, $response, ['nope']); + + static::assertSame(302, $result->getStatusCode()); + static::assertSame(['/subfolder/controller?query=param&other=2'], $result->getHeader('location')); + } + + /** + * Test redirectFromReferer() - With a loop term matching the referer in its path -> redirect to default + */ + public function testRedirectFromRefererWithMatchingLoopTermInPath(): void + { + $this->container->environment['HTTP_REFERER'] = 'http://shaarli.tld/subfolder/controller?query=param&other=2'; + + $response = new Response(); + + $result = $this->controller->redirectFromReferer($this->request, $response, ['nope', 'controller']); + + static::assertSame(302, $result->getStatusCode()); + static::assertSame(['/subfolder/'], $result->getHeader('location')); + } + + /** + * Test redirectFromReferer() - With a loop term matching the referer in its query parameters -> redirect to default + */ + public function testRedirectFromRefererWithMatchingLoopTermInQueryParam(): void + { + $this->container->environment['HTTP_REFERER'] = 'http://shaarli.tld/subfolder/controller?query=param&other=2'; + + $response = new Response(); + + $result = $this->controller->redirectFromReferer($this->request, $response, ['nope', 'other']); + + static::assertSame(302, $result->getStatusCode()); + static::assertSame(['/subfolder/'], $result->getHeader('location')); + } + + /** + * Test redirectFromReferer() - With a loop term matching the referer in its query value + * -> we do not block redirection for query parameter values. + */ + public function testRedirectFromRefererWithMatchingLoopTermInQueryValue(): void + { + $this->container->environment['HTTP_REFERER'] = 'http://shaarli.tld/subfolder/controller?query=param&other=2'; + + $response = new Response(); + + $result = $this->controller->redirectFromReferer($this->request, $response, ['nope', 'param']); + + static::assertSame(302, $result->getStatusCode()); + static::assertSame(['/subfolder/controller?query=param&other=2'], $result->getHeader('location')); + } + + /** + * Test redirectFromReferer() - With a loop term matching the referer in its domain name + * -> we do not block redirection for shaarli's hosts + */ + public function testRedirectFromRefererWithLoopTermInDomain(): void + { + $this->container->environment['HTTP_REFERER'] = 'http://shaarli.tld/subfolder/controller?query=param&other=2'; + + $response = new Response(); + + $result = $this->controller->redirectFromReferer($this->request, $response, ['shaarli']); + + static::assertSame(302, $result->getStatusCode()); + static::assertSame(['/subfolder/controller?query=param&other=2'], $result->getHeader('location')); + } + + /** + * Test redirectFromReferer() - With a loop term matching a query parameter AND clear this query param + * -> the param should be cleared before checking if it matches the redir loop terms + */ + public function testRedirectFromRefererWithMatchingClearedParam(): void + { + $this->container->environment['HTTP_REFERER'] = 'http://shaarli.tld/subfolder/controller?query=param&other=2'; + + $response = new Response(); + + $result = $this->controller->redirectFromReferer($this->request, $response, ['query'], ['query']); + + static::assertSame(302, $result->getStatusCode()); + static::assertSame(['/subfolder/controller?other=2'], $result->getHeader('location')); + } +} diff --git a/tpl/default/404.html b/tpl/default/404.html index 09737b4b..7b696e4c 100644 --- a/tpl/default/404.html +++ b/tpl/default/404.html @@ -8,7 +8,7 @@ {include="page.header"}

{'Sorry, nothing to see here.'|t}

- +

{$error_message}

{include="page.footer"} diff --git a/tpl/default/addlink.html b/tpl/default/addlink.html index 999d2f4d..c37827f4 100644 --- a/tpl/default/addlink.html +++ b/tpl/default/addlink.html @@ -9,7 +9,7 @@