From a20438b901b227fd597fec66ffa2b7b8abc30cfb Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 7 Feb 2026 23:01:13 +0000 Subject: [PATCH] Theme System: Fixed theme view before/after issues - Updated the system to work with modules. - Updated module docs to consider namespacing. - Fixed view loading and registration event ordering. - Fixed checking if views are registered. --- app/App/Providers/ThemeServiceProvider.php | 4 ++-- app/Theming/ThemeViews.php | 23 ++++++++++++------- dev/docs/theme-system-modules.md | 3 +++ ...emeModuleTests.php => ThemeModuleTest.php} | 15 +++++++++++- 4 files changed, 34 insertions(+), 11 deletions(-) rename tests/Theme/{ThemeModuleTests.php => ThemeModuleTest.php} (92%) diff --git a/app/App/Providers/ThemeServiceProvider.php b/app/App/Providers/ThemeServiceProvider.php index 50c76bbf8..cca1ca236 100644 --- a/app/App/Providers/ThemeServiceProvider.php +++ b/app/App/Providers/ThemeServiceProvider.php @@ -35,9 +35,9 @@ class ThemeServiceProvider extends ServiceProvider $themeService->readThemeActions(); $themeService->dispatch(ThemeEvents::APP_BOOT, $this->app); - $themeViews = new ThemeViews(); + $themeViews = new ThemeViews($viewFactory->getFinder()); + $themeViews->registerViewPathsForTheme($themeService->getModules()); $themeService->dispatch(ThemeEvents::THEME_REGISTER_VIEWS, $themeViews); - $themeViews->registerViewPathsForTheme($viewFactory->getFinder(), $themeService->getModules()); if ($themeViews->hasRegisteredViews()) { $viewFactory->share('__themeViews', $themeViews); Blade::directive('include', function ($expression) { diff --git a/app/Theming/ThemeViews.php b/app/Theming/ThemeViews.php index b2d0adc02..88769bae1 100644 --- a/app/Theming/ThemeViews.php +++ b/app/Theming/ThemeViews.php @@ -17,21 +17,26 @@ class ThemeViews */ protected array $afterViews = []; + public function __construct( + protected FileViewFinder $finder + ) { + } + /** * Register any extra paths for where we may expect views to be located - * with the provided FileViewFinder, to make custom views available for use. + * with the FileViewFinder, to make custom views available for use. * @param ThemeModule[] $modules */ - public function registerViewPathsForTheme(FileViewFinder $finder, array $modules): void + public function registerViewPathsForTheme(array $modules): void { foreach ($modules as $module) { $moduleViewsPath = $module->path('views'); if (file_exists($moduleViewsPath) && is_dir($moduleViewsPath)) { - $finder->prependLocation($moduleViewsPath); + $this->finder->prependLocation($moduleViewsPath); } } - $finder->prependLocation(theme_path()); + $this->finder->prependLocation(theme_path()); } /** @@ -70,19 +75,21 @@ class ThemeViews public function hasRegisteredViews(): bool { - return !empty($this->beforeViews) && !empty($this->afterViews); + return !empty($this->beforeViews) || !empty($this->afterViews); } protected function registerAdjacentView(array &$location, string $targetView, string $localView, int $priority = 50): void { - $viewPath = theme_path($localView . '.blade.php'); - if (!file_exists($viewPath)) { - throw new ThemeException("Expected registered view file at \"{$viewPath}\" does not exist"); + try { + $viewPath = $this->finder->find($localView); + } catch (\InvalidArgumentException $exception) { + throw new ThemeException("Expected registered view file with name \"{$localView}\" could not be found."); } if (!isset($location[$targetView])) { $location[$targetView] = []; } + $location[$targetView][$viewPath] = $priority; } diff --git a/dev/docs/theme-system-modules.md b/dev/docs/theme-system-modules.md index a52e3d25f..10eec2275 100644 --- a/dev/docs/theme-system-modules.md +++ b/dev/docs/theme-system-modules.md @@ -58,6 +58,9 @@ Here are some general best practices when it comes to creating modules: - Use a unique name and clear description so the user can understand the purpose of the module. - Increment the metadata version on change, keeping to [semver](https://semver.org/) to indicate compatibility of new versions. - Where possible, prefer to [insert views before/after](logical-theme-system.md#custom-view-registration-example) instead of overriding existing views, to reduce likelihood of conflicts or update troubles. +- When using/registering custom views, use some level of unique namespacing within the view path to prevent potential conflicts with other customizations. + - For example, I may store a view within my module as `views/my-module-name-welcome.blade.php`, to be registered as 'my-module-name-welcome'. + - This is important since views may be resolved from other modules or the active theme, which may/will override your module level view. ### Distribution Format diff --git a/tests/Theme/ThemeModuleTests.php b/tests/Theme/ThemeModuleTest.php similarity index 92% rename from tests/Theme/ThemeModuleTests.php rename to tests/Theme/ThemeModuleTest.php index a7d317dce..b2f912dd7 100644 --- a/tests/Theme/ThemeModuleTests.php +++ b/tests/Theme/ThemeModuleTest.php @@ -5,7 +5,7 @@ namespace Tests\Theme; use BookStack\Facades\Theme; use Tests\TestCase; -class ThemeModuleTests extends TestCase +class ThemeModuleTest extends TestCase { public function test_modules_loaded_on_theme_load() { @@ -207,6 +207,19 @@ class ThemeModuleTests extends TestCase }); } + public function test_module_can_use_theme_view_render_functions() + { + $this->usingModuleFolder(function (string $moduleFolderPath) { + file_put_contents($moduleFolderPath . '/functions.php', " \$views->renderBefore('layouts.parts.header', 'cat', 100));"); + mkdir($moduleFolderPath . '/views', 0777, true); + file_put_contents($moduleFolderPath . '/views/cat.blade.php', 'mysupercatispouncy'); + + $this->refreshApplication(); + + $this->asAdmin()->get('/')->assertSee('mysupercatispouncy'); + }); + } + protected function usingModuleFolder(callable $callback): void { $this->usingThemeFolder(function (string $themeFolder) use ($callback) {