diff --git a/app/Exceptions/ThemeException.php b/app/Exceptions/ThemeException.php new file mode 100644 index 000000000..b721effe2 --- /dev/null +++ b/app/Exceptions/ThemeException.php @@ -0,0 +1,7 @@ + + */ + protected array $listeners = []; /** * Listen to a given custom theme event, * setting up the action to be ran when the event occurs. */ - public function listen(string $event, callable $action) + public function listen(string $event, callable $action): void { if (!isset($this->listeners[$event])) { $this->listeners[$event] = []; @@ -31,10 +35,8 @@ class ThemeService * * If a callback returns a non-null value, this method will * stop and return that value itself. - * - * @return mixed */ - public function dispatch(string $event, ...$args) + public function dispatch(string $event, ...$args): mixed { foreach ($this->listeners[$event] ?? [] as $action) { $result = call_user_func_array($action, $args); @@ -49,7 +51,7 @@ class ThemeService /** * Register a new custom artisan command to be available. */ - public function registerCommand(Command $command) + public function registerCommand(Command $command): void { Artisan::starting(function (Application $application) use ($command) { $application->addCommands([$command]); @@ -59,18 +61,22 @@ class ThemeService /** * Read any actions from the set theme path if the 'functions.php' file exists. */ - public function readThemeActions() + public function readThemeActions(): void { $themeActionsFile = theme_path('functions.php'); if ($themeActionsFile && file_exists($themeActionsFile)) { - require $themeActionsFile; + try { + require $themeActionsFile; + } catch (\Error $exception) { + throw new ThemeException("Failed loading theme functions file at \"{$themeActionsFile}\" with error: {$exception->getMessage()}"); + } } } /** * @see SocialAuthService::addSocialDriver */ - public function addSocialDriver(string $driverName, array $config, string $socialiteHandler, callable $configureForRedirect = null) + public function addSocialDriver(string $driverName, array $config, string $socialiteHandler, callable $configureForRedirect = null): void { $socialAuthService = app()->make(SocialAuthService::class); $socialAuthService->addSocialDriver($driverName, $config, $socialiteHandler, $configureForRedirect); diff --git a/resources/views/layouts/base.blade.php b/resources/views/layouts/base.blade.php index e0a6f46d0..8875788a6 100644 --- a/resources/views/layouts/base.blade.php +++ b/resources/views/layouts/base.blade.php @@ -65,7 +65,9 @@ @yield('bottom') - + @if($cspNonce ?? false) + + @endif @yield('scripts') @include('layouts.parts.base-body-end') diff --git a/tests/ThemeTest.php b/tests/ThemeTest.php index 08c99d297..f0266cd0c 100644 --- a/tests/ThemeTest.php +++ b/tests/ThemeTest.php @@ -8,6 +8,7 @@ use BookStack\Activity\Models\Webhook; use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Page; use BookStack\Entities\Tools\PageContent; +use BookStack\Exceptions\ThemeException; use BookStack\Facades\Theme; use BookStack\Theming\ThemeEvents; use BookStack\Users\Models\User; @@ -51,6 +52,19 @@ class ThemeTest extends TestCase }); } + public function test_theme_functions_loads_errors_are_caught_and_logged() + { + $this->usingThemeFolder(function ($themeFolder) { + $functionsFile = theme_path('functions.php'); + file_put_contents($functionsFile, "expectException(ThemeException::class); + $this->expectExceptionMessageMatches('/Failed loading theme functions file at ".*?" with error: Class "BookStack\\\\Biscuits" not found/'); + + $this->runWithEnv('APP_THEME', $themeFolder, fn() => null); + }); + } + public function test_event_commonmark_environment_configure() { $callbackCalled = false;