From 818b3193ffabec57501e3bdfa997206e3c0671ef Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Sat, 13 Jun 2020 11:22:14 +0200 Subject: [PATCH] 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 + ...t.php => ShaarliVisitorControllerTest.php} | 22 +++---- 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 ++-- 51 files changed, 205 insertions(+), 236 deletions(-) rename tests/front/controller/visitor/{ShaarliPublicControllerTest.php => ShaarliVisitorControllerTest.php} (92%) 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 @@ * 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 @@ public function __construct(ShaarliContainer $container) 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 @@ protected function render(string $template): string $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 @@ protected function redirectFromReferer( 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 @@ private function initialize() */ 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\Render\PageBuilder; use Slim\Http\Request; use Slim\Http\Response; +use Slim\Http\Uri; class ShaarliMiddlewareTest extends TestCase { @@ -29,6 +30,13 @@ public function setUp(): void 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 @@ public function testMiddlewareExecution(): void 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\Thumbnailer; use Slim\Http\Request; use Slim\Http\Response; -use Slim\Http\Uri; class PostBookmarkControllerTest extends TestCase { @@ -406,12 +405,6 @@ public function testSaveBookmark(): void 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 @@ public function testSaveExistingBookmark(): void 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 @@ public function testSaveBookmarkWithThumbnail(): void 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\SessionManager; use Slim\Http\Request; use Slim\Http\Response; -use Slim\Http\Uri; class SessionFilterControllerTest extends TestCase { @@ -33,12 +32,6 @@ public function testLinksPerPage(): void $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 @@ public function testLinksPerPage(): void 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 @@ public function testLinksPerPageNotValid(): void 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 @@ public function testVisibility(): 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(); $result = $this->controller->visibility($request, $response, $arg); @@ -141,12 +122,6 @@ public function testVisibilityToggleOff(): 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(); $result = $this->controller->visibility($request, $response, $arg); @@ -176,19 +151,13 @@ public function testVisibilitySwitch(): 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(); $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 @@ public function testVisibilityInvalidValue(): 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(); $result = $this->controller->visibility($request, $response, $arg); @@ -249,12 +212,6 @@ public function testVisibilityLoggedOut(): 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(); $result = $this->controller->visibility($request, $response, $arg); @@ -272,12 +229,6 @@ public function testUntaggedOnly(): void $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 @@ public function testUntaggedOnlyToggleOff(): void $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 @@ protected function createContainer(): void '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/ShaarliVisitorControllerTest.php similarity index 92% rename from tests/front/controller/visitor/ShaarliPublicControllerTest.php rename to tests/front/controller/visitor/ShaarliVisitorControllerTest.php index 899b280b..83d08358 100644 --- a/tests/front/controller/visitor/ShaarliPublicControllerTest.php +++ b/tests/front/controller/visitor/ShaarliVisitorControllerTest.php @@ -8,15 +8,14 @@ use Shaarli\Bookmark\BookmarkFilter; use Slim\Http\Request; use Slim\Http\Response; -use Slim\Http\Uri; /** * Class ShaarliControllerTest * - * This class is used to test default behavior of ShaarliController abstract class. + * This class is used to test default behavior of ShaarliVisitorController abstract class. * It uses a dummy non abstract controller. */ -class ShaarliPublicControllerTest extends TestCase +class ShaarliVisitorControllerTest extends TestCase { use FrontControllerMockHelper; @@ -49,20 +48,15 @@ public function redirectFromReferer( Request $request, Response $response, array $loopTerms = [], - array $clearParams = [] + array $clearParams = [], + string $anchor = null ): Response { - return parent::redirectFromReferer($request, $response, $loopTerms, $clearParams); + return parent::redirectFromReferer($request, $response, $loopTerms, $clearParams, $anchor); } }; $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 @@ -102,6 +96,8 @@ public function testRender(): void 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']); @@ -153,7 +149,7 @@ public function testRedirectFromRefererWithMatchingLoopTermInPath(): void $result = $this->controller->redirectFromReferer($this->request, $response, ['nope', 'controller']); static::assertSame(302, $result->getStatusCode()); - static::assertSame(['/subfolder'], $result->getHeader('location')); + static::assertSame(['/subfolder/'], $result->getHeader('location')); } /** @@ -168,7 +164,7 @@ public function testRedirectFromRefererWithMatchingLoopTermInQueryParam(): void $result = $this->controller->redirectFromReferer($this->request, $response, ['nope', 'other']); static::assertSame(302, $result->getStatusCode()); - static::assertSame(['/subfolder'], $result->getHeader('location')); + static::assertSame(['/subfolder/'], $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 @@