Skip to content

Commit 1b17bb3

Browse files
committed
Theme: Changed how before/after views are registered
Changed the system out to be a theme event instead of method, to align with other registration events, and so that the theme view work can better be contained in its own class.
1 parent 9fcfc76 commit 1b17bb3

5 files changed

Lines changed: 135 additions & 100 deletions

File tree

app/App/Providers/ThemeServiceProvider.php

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44

55
use BookStack\Theming\ThemeEvents;
66
use BookStack\Theming\ThemeService;
7+
use BookStack\Theming\ThemeViews;
78
use Illuminate\Support\Facades\Blade;
89
use Illuminate\Support\ServiceProvider;
9-
use Illuminate\View\View;
1010

1111
class ThemeServiceProvider extends ServiceProvider
1212
{
@@ -26,16 +26,21 @@ public function boot(): void
2626
{
2727
// Boot up the theme system
2828
$themeService = $this->app->make(ThemeService::class);
29-
3029
$viewFactory = $this->app->make('view');
31-
$themeService->registerViewPathsForTheme($viewFactory->getFinder());
30+
if (!$themeService->getTheme()) {
31+
return;
32+
}
33+
34+
$themeService->readThemeActions();
35+
$themeService->dispatch(ThemeEvents::APP_BOOT, $this->app);
3236

33-
if ($themeService->logicalThemeIsActive()) {
34-
$themeService->readThemeActions();
35-
$themeService->dispatch(ThemeEvents::APP_BOOT, $this->app);
36-
$viewFactory->share('__theme', $themeService);
37+
$themeViews = new ThemeViews();
38+
$themeService->dispatch(ThemeEvents::THEME_REGISTER_VIEWS, $themeViews);
39+
$themeViews->registerViewPathsForTheme($viewFactory->getFinder());
40+
if ($themeViews->hasRegisteredViews()) {
41+
$viewFactory->share('__themeViews', $themeViews);
3742
Blade::directive('include', function ($expression) {
38-
return "<?php echo \$__theme->handleViewInclude({$expression}, array_diff_key(get_defined_vars(), ['__data' => 1, '__path' => 1])); ?>";
43+
return "<?php echo \$__themeViews->handleViewInclude({$expression}, array_diff_key(get_defined_vars(), ['__data' => 1, '__path' => 1])); ?>";
3944
});
4045
}
4146
}

app/Theming/ThemeEvents.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,16 @@ class ThemeEvents
134134
*/
135135
const ROUTES_REGISTER_WEB_AUTH = 'routes_register_web_auth';
136136

137+
138+
/**
139+
* Theme register views event.
140+
* Called by the theme system when a theme is active, so that custom view templates can be registered
141+
* to be rendered in addition to existing app views.
142+
*
143+
* @param \BookStack\Theming\ThemeViews $themeViews
144+
*/
145+
const THEME_REGISTER_VIEWS = 'theme_register_views';
146+
137147
/**
138148
* Web before middleware action.
139149
* Runs before the request is handled but after all other middleware apart from those

app/Theming/ThemeService.php

Lines changed: 4 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,6 @@ class ThemeService
1616
*/
1717
protected array $listeners = [];
1818

19-
/**
20-
* @var array<string, array<string, int>>
21-
*/
22-
protected array $beforeViews = [];
23-
24-
/**
25-
* @var array<string, array<string, int>>
26-
*/
27-
protected array $afterViews = [];
28-
2919
/**
3020
* Get the currently configured theme.
3121
* Returns an empty string if not configured.
@@ -92,31 +82,17 @@ public function registerCommand(Command $command): void
9282
public function readThemeActions(): void
9383
{
9484
$themeActionsFile = theme_path('functions.php');
85+
if (!$themeActionsFile || !file_exists($themeActionsFile)) {
86+
return;
87+
}
88+
9589
try {
9690
require $themeActionsFile;
9791
} catch (\Error $exception) {
9892
throw new ThemeException("Failed loading theme functions file at \"{$themeActionsFile}\" with error: {$exception->getMessage()}");
9993
}
10094
}
10195

102-
/**
103-
* Check if a logical theme is active.
104-
*/
105-
public function logicalThemeIsActive(): bool
106-
{
107-
$themeActionsFile = theme_path('functions.php');
108-
return $themeActionsFile && file_exists($themeActionsFile);
109-
}
110-
111-
/**
112-
* Register any extra paths for where we may expect views to be located
113-
* with the provided FileViewFinder, to make custom views available for use.
114-
*/
115-
public function registerViewPathsForTheme(FileViewFinder $finder): void
116-
{
117-
$finder->prependLocation(theme_path());
118-
}
119-
12096
/**
12197
* @see SocialDriverManager::addSocialDriver
12298
*/
@@ -125,63 +101,4 @@ public function addSocialDriver(string $driverName, array $config, string $socia
125101
$driverManager = app()->make(SocialDriverManager::class);
126102
$driverManager->addSocialDriver($driverName, $config, $socialiteHandler, $configureForRedirect);
127103
}
128-
129-
/**
130-
* Provide the response for a blade template view include.
131-
*/
132-
public function handleViewInclude(string $viewPath, array $data = []): string
133-
{
134-
$viewsContent = [
135-
...$this->renderViewSets($this->beforeViews[$viewPath] ?? [], $data),
136-
view()->make($viewPath, $data)->render(),
137-
...$this->renderViewSets($this->afterViews[$viewPath] ?? [], $data),
138-
];
139-
140-
return implode("\n", $viewsContent);
141-
}
142-
143-
/**
144-
* Register a custom view to be rendered before the given target view is included in the template system.
145-
*/
146-
public function registerViewToRenderBefore(string $targetView, string $localView, int $priority = 50): void
147-
{
148-
$this->registerAdjacentView($this->beforeViews, $targetView, $localView, $priority);
149-
}
150-
151-
/**
152-
* Register a custom view to be rendered after the given target view is included in the template system.
153-
*/
154-
public function registerViewToRenderAfter(string $targetView, string $localView, int $priority = 50): void
155-
{
156-
$this->registerAdjacentView($this->afterViews, $targetView, $localView, $priority);
157-
}
158-
159-
protected function registerAdjacentView(array &$location, string $targetView, string $localView, int $priority = 50): void
160-
{
161-
$viewPath = theme_path($localView . '.blade.php');
162-
if (!file_exists($viewPath)) {
163-
throw new ThemeException("Expected registered view file at \"{$viewPath}\" does not exist");
164-
}
165-
166-
if (!isset($location[$targetView])) {
167-
$location[$targetView] = [];
168-
}
169-
$location[$targetView][$viewPath] = $priority;
170-
}
171-
172-
/**
173-
* @param array<string, int> $viewSet
174-
* @return string[]
175-
*/
176-
protected function renderViewSets(array $viewSet, array $data): array
177-
{
178-
$paths = array_keys($viewSet);
179-
usort($paths, function (string $a, string $b) use ($viewSet) {
180-
return $viewSet[$a] <=> $viewSet[$b];
181-
});
182-
183-
return array_map(function (string $viewPath) use ($data) {
184-
return view()->file($viewPath, $data)->render();
185-
}, $paths);
186-
}
187104
}

app/Theming/ThemeViews.php

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
<?php
2+
3+
namespace BookStack\Theming;
4+
5+
use BookStack\Exceptions\ThemeException;
6+
use Illuminate\View\FileViewFinder;
7+
8+
class ThemeViews
9+
{
10+
/**
11+
* @var array<string, array<string, int>>
12+
*/
13+
protected array $beforeViews = [];
14+
15+
/**
16+
* @var array<string, array<string, int>>
17+
*/
18+
protected array $afterViews = [];
19+
20+
/**
21+
* Register any extra paths for where we may expect views to be located
22+
* with the provided FileViewFinder, to make custom views available for use.
23+
*/
24+
public function registerViewPathsForTheme(FileViewFinder $finder): void
25+
{
26+
$finder->prependLocation(theme_path());
27+
}
28+
29+
/**
30+
* Provide the response for a blade template view include.
31+
*/
32+
public function handleViewInclude(string $viewPath, array $data = []): string
33+
{
34+
if (!$this->hasRegisteredViews()) {
35+
return view()->make($viewPath, $data)->render();
36+
}
37+
38+
$viewsContent = [
39+
...$this->renderViewSets($this->beforeViews[$viewPath] ?? [], $data),
40+
view()->make($viewPath, $data)->render(),
41+
...$this->renderViewSets($this->afterViews[$viewPath] ?? [], $data),
42+
];
43+
44+
return implode("\n", $viewsContent);
45+
}
46+
47+
/**
48+
* Register a custom view to be rendered before the given target view is included in the template system.
49+
*/
50+
public function renderBefore(string $targetView, string $localView, int $priority = 50): void
51+
{
52+
$this->registerAdjacentView($this->beforeViews, $targetView, $localView, $priority);
53+
}
54+
55+
/**
56+
* Register a custom view to be rendered after the given target view is included in the template system.
57+
*/
58+
public function renderAfter(string $targetView, string $localView, int $priority = 50): void
59+
{
60+
$this->registerAdjacentView($this->afterViews, $targetView, $localView, $priority);
61+
}
62+
63+
public function hasRegisteredViews(): bool
64+
{
65+
return !empty($this->beforeViews) && !empty($this->afterViews);
66+
}
67+
68+
protected function registerAdjacentView(array &$location, string $targetView, string $localView, int $priority = 50): void
69+
{
70+
$viewPath = theme_path($localView . '.blade.php');
71+
if (!file_exists($viewPath)) {
72+
throw new ThemeException("Expected registered view file at \"{$viewPath}\" does not exist");
73+
}
74+
75+
if (!isset($location[$targetView])) {
76+
$location[$targetView] = [];
77+
}
78+
$location[$targetView][$viewPath] = $priority;
79+
}
80+
81+
/**
82+
* @param array<string, int> $viewSet
83+
* @return string[]
84+
*/
85+
protected function renderViewSets(array $viewSet, array $data): array
86+
{
87+
$paths = array_keys($viewSet);
88+
usort($paths, function (string $a, string $b) use ($viewSet) {
89+
return $viewSet[$a] <=> $viewSet[$b];
90+
});
91+
92+
return array_map(function (string $viewPath) use ($data) {
93+
return view()->file($viewPath, $data)->render();
94+
}, $paths);
95+
}
96+
}

tests/ThemeTest.php

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -492,7 +492,7 @@ public function test_public_folder_contents_accessible_via_route()
492492
});
493493
}
494494

495-
public function test_register_view_to_render_before_and_after()
495+
public function test_theme_register_views_event_to_insert_views_before_and_after()
496496
{
497497
$this->usingThemeFolder(function (string $folder) {
498498
$before = 'this-is-my-before-header-string';
@@ -502,10 +502,14 @@ public function test_register_view_to_render_before_and_after()
502502

503503
$functionsContent = <<<'CONTENT'
504504
<?php use BookStack\Facades\Theme;
505-
Theme::registerViewToRenderBefore('layouts.parts.header', 'before', 4);
506-
Theme::registerViewToRenderAfter('layouts.parts.header', 'after-a', 4);
507-
Theme::registerViewToRenderAfter('layouts.parts.header', 'after-b', 1);
508-
Theme::registerViewToRenderAfter('layouts.parts.header', 'after-c', 12);
505+
use BookStack\Theming\ThemeEvents;
506+
use BookStack\Theming\ThemeViews;
507+
Theme::listen(ThemeEvents::THEME_REGISTER_VIEWS, function (ThemeViews $themeViews) {
508+
$themeViews->renderBefore('layouts.parts.header', 'before', 4);
509+
$themeViews->renderAfter('layouts.parts.header', 'after-a', 4);
510+
$themeViews->renderAfter('layouts.parts.header', 'after-b', 1);
511+
$themeViews->renderAfter('layouts.parts.header', 'after-c', 12);
512+
});
509513
CONTENT;
510514

511515
$viewDir = theme_path();
@@ -516,12 +520,15 @@ public function test_register_view_to_render_before_and_after()
516520
file_put_contents($viewDir . '/after-c.blade.php', $afterC);
517521

518522
$this->refreshApplication();
523+
$this->artisan('view:clear');
519524

520525
$resp = $this->get('/login');
521526
$resp->assertSee($before);
522527
// Ensure ordering of the multiple after views
523528
$resp->assertSee($afterB . "\n" . $afterA . "\nthis-is-my-after-header-string-52");
524529
});
530+
531+
$this->artisan('view:clear');
525532
}
526533

527534
protected function usingThemeFolder(callable $callback)

0 commit comments

Comments
 (0)