diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 075c98ec7..694036ab4 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -28,6 +28,7 @@ class Kernel extends HttpKernel \Illuminate\Session\Middleware\StartSession::class, \Illuminate\View\Middleware\ShareErrorsFromSession::class, \BookStack\Http\Middleware\VerifyCsrfToken::class, + \BookStack\Http\Middleware\RunThemeActions::class, \BookStack\Http\Middleware\Localization::class, ], 'api' => [ diff --git a/app/Http/Middleware/RunThemeActions.php b/app/Http/Middleware/RunThemeActions.php new file mode 100644 index 000000000..d995f1445 --- /dev/null +++ b/app/Http/Middleware/RunThemeActions.php @@ -0,0 +1,29 @@ +<?php + +namespace BookStack\Http\Middleware; + +use BookStack\Facades\Theme; +use BookStack\Theming\ThemeEvents; +use Closure; + +class RunThemeActions +{ + /** + * Handle an incoming request. + * + * @param \Illuminate\Http\Request $request + * @param \Closure $next + * @return mixed + */ + public function handle($request, Closure $next) + { + $earlyResponse = Theme::dispatch(ThemeEvents::WEB_MIDDLEWARE_BEFORE, $request); + if (!is_null($earlyResponse)) { + return $earlyResponse; + } + + $response = $next($request); + $response = Theme::dispatch(ThemeEvents::WEB_MIDDLEWARE_AFTER, $request, $response) ?? $response; + return $response; + } +} diff --git a/app/Theming/ThemeEvents.php b/app/Theming/ThemeEvents.php index 753b13d91..a81179ed7 100644 --- a/app/Theming/ThemeEvents.php +++ b/app/Theming/ThemeEvents.php @@ -20,6 +20,27 @@ class ThemeEvents */ const APP_BOOT = 'app_boot'; + /** + * Web before middleware action. + * Runs before the request is handled but after all other middleware apart from those + * that depend on the current session user (Localization for example). + * Provides the original request to use. + * Return values, if provided, will be used as a new response to use. + * @param \Illuminate\Http\Request $request + * @returns \Illuminate\Http\Response|null + */ + const WEB_MIDDLEWARE_BEFORE = 'web_middleware_before'; + + /** + * Web after middleware action. + * Runs after the request is handled but before the response is sent. + * Provides both the original request and the currently resolved response. + * Return values, if provided, will be used as a new response to use. + * @param \Illuminate\Http\Request $request + * @returns \Illuminate\Http\Response|null + */ + const WEB_MIDDLEWARE_AFTER = 'web_middleware_after'; + /** * Commonmark environment configure. * Provides the commonmark library environment for customization diff --git a/tests/ThemeTest.php b/tests/ThemeTest.php index 8faf7711e..82a4625ac 100644 --- a/tests/ThemeTest.php +++ b/tests/ThemeTest.php @@ -5,6 +5,8 @@ use BookStack\Entities\Tools\PageContent; use BookStack\Facades\Theme; use BookStack\Theming\ThemeEvents; use File; +use Illuminate\Http\Request; +use Illuminate\Http\Response; use League\CommonMark\ConfigurableEnvironmentInterface; class ThemeTest extends TestCase @@ -14,7 +16,7 @@ class ThemeTest extends TestCase public function test_translation_text_can_be_overridden_via_theme() { - $this->usingThemeFolder(function() { + $this->usingThemeFolder(function () { $translationPath = theme_path('/lang/en'); File::makeDirectory($translationPath, 0777, true); @@ -30,11 +32,11 @@ class ThemeTest extends TestCase public function test_theme_functions_file_used_and_app_boot_event_runs() { - $this->usingThemeFolder(function($themeFolder) { + $this->usingThemeFolder(function ($themeFolder) { $functionsFile = theme_path('functions.php'); app()->alias('cat', 'dog'); file_put_contents($functionsFile, "<?php\nTheme::listen(\BookStack\Theming\ThemeEvents::APP_BOOT, function(\$app) { \$app->alias('cat', 'dog');});"); - $this->runWithEnv('APP_THEME', $themeFolder, function() { + $this->runWithEnv('APP_THEME', $themeFolder, function () { $this->assertEquals('cat', $this->app->getAlias('dog')); }); }); @@ -43,7 +45,7 @@ class ThemeTest extends TestCase public function test_event_commonmark_environment_configure() { $callbackCalled = false; - $callback = function($environment) use (&$callbackCalled) { + $callback = function ($environment) use (&$callbackCalled) { $this->assertInstanceOf(ConfigurableEnvironmentInterface::class, $environment); $callbackCalled = true; return $environment; @@ -57,6 +59,69 @@ class ThemeTest extends TestCase $this->assertTrue($callbackCalled); } + public function test_event_web_middleware_before() + { + $callbackCalled = false; + $requestParam = null; + $callback = function ($request) use (&$callbackCalled, &$requestParam) { + $requestParam = $request; + $callbackCalled = true; + }; + + Theme::listen(ThemeEvents::WEB_MIDDLEWARE_BEFORE, $callback); + $this->get('/login', ['Donkey' => 'cat']); + + $this->assertTrue($callbackCalled); + $this->assertInstanceOf(Request::class, $requestParam); + $this->assertEquals('cat', $requestParam->header('donkey')); + } + + public function test_event_web_middleware_before_return_val_used_as_response() + { + $callback = function (Request $request) { + return response('cat', 412); + }; + + Theme::listen(ThemeEvents::WEB_MIDDLEWARE_BEFORE, $callback); + $resp = $this->get('/login', ['Donkey' => 'cat']); + $resp->assertSee('cat'); + $resp->assertStatus(412); + } + + public function test_event_web_middleware_after() + { + $callbackCalled = false; + $requestParam = null; + $responseParam = null; + $callback = function ($request, Response $response) use (&$callbackCalled, &$requestParam, &$responseParam) { + $requestParam = $request; + $responseParam = $response; + $callbackCalled = true; + $response->header('donkey', 'cat123'); + }; + + Theme::listen(ThemeEvents::WEB_MIDDLEWARE_AFTER, $callback); + + $resp = $this->get('/login', ['Donkey' => 'cat']); + $this->assertTrue($callbackCalled); + $this->assertInstanceOf(Request::class, $requestParam); + $this->assertInstanceOf(Response::class, $responseParam); + $resp->assertHeader('donkey', 'cat123'); + } + + public function test_event_web_middleware_after_return_val_used_as_response() + { + $callback = function () { + return response('cat456', 443); + }; + + Theme::listen(ThemeEvents::WEB_MIDDLEWARE_AFTER, $callback); + + $resp = $this->get('/login', ['Donkey' => 'cat']); + $resp->assertSee('cat456'); + $resp->assertStatus(443); + } + protected function usingThemeFolder(callable $callback) { // Create a folder and configure a theme