Skip to content

Commit 151823b

Browse files
committed
Theme Modules: Added easier way to insert HTML head content
1 parent 27240be commit 151823b

File tree

5 files changed

+57
-3
lines changed

5 files changed

+57
-3
lines changed

app/Theming/CustomHtmlHeadContentProvider.php

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ class CustomHtmlHeadContentProvider
1212
{
1313
public function __construct(
1414
protected CspService $cspService,
15-
protected Cache $cache
15+
protected Cache $cache,
16+
protected ThemeService $themeService,
1617
) {
1718
}
1819

@@ -23,8 +24,9 @@ public function __construct(
2324
public function forWeb(): string
2425
{
2526
$content = $this->getSourceContent();
26-
$hash = md5($content);
27+
$hash = md5($content) . ':' . $this->themeService->getModulesHash();
2728
$html = $this->cache->remember('custom-head-web:' . $hash, 86400, function () use ($content) {
29+
$content .= "\n" . $this->getModuleHeadContent();
2830
return HtmlNonceApplicator::prepare($content);
2931
});
3032

@@ -53,4 +55,23 @@ protected function getSourceContent(): string
5355
{
5456
return setting('app-custom-head', '');
5557
}
58+
59+
/**
60+
* Get any custom head content from installed modules.
61+
*/
62+
protected function getModuleHeadContent(): string
63+
{
64+
$content = '';
65+
foreach ($this->themeService->getModules() as $module) {
66+
$headContentPath = $module->path('head');
67+
if (file_exists($headContentPath) && is_dir($headContentPath)) {
68+
$htmlFiles = glob($headContentPath . '/*.html');
69+
foreach ($htmlFiles as $file) {
70+
$content .= file_get_contents($file);
71+
}
72+
}
73+
}
74+
75+
return $content;
76+
}
5677
}

app/Theming/ThemeService.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,20 @@ public function getModules(): array
126126
return $this->modules;
127127
}
128128

129+
/**
130+
* Get a hash to represent the currently loaded modules.
131+
*/
132+
public function getModulesHash(): string
133+
{
134+
$key = "";
135+
136+
foreach ($this->modules as $module) {
137+
$key .= $module->name . ':' . $module->version . ';';
138+
}
139+
140+
return md5($key);
141+
}
142+
129143
/**
130144
* Look for a specific file within the theme or its modules.
131145
* Returns the first file found or null if not found.

dev/docs/theme-system-modules.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ The content within the module folder should then follow this format:
2424

2525
- `bookstack-module.json` - REQUIRED - A JSON file containing [the metadata](#module-json-metadata) for the module.
2626
- `functions.php` - OPTIONAL - A PHP file containing code for the [logical theme system](logical-theme-system.md).
27+
- `head/` - OPTIONAL - A folder containing HTML files which will be included into the HTML head of app-views.
2728
- `icons/` - OPTIONAL - A folder containing any icons to use as per [the visual theme system](visual-theme-system.md#customizing-icons).
2829
- `lang/` - OPTIONAL - A folder containing any language files to use as per [the visual theme system](visual-theme-system.md#customizing-text-content).
2930
- `public/` - OPTIONAL - A folder containing any files to expose into public web-space as per [the visual theme system](visual-theme-system.md#publicly-accessible-files).

resources/views/layouts/parts/custom-head.blade.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
@inject('headContent', 'BookStack\Theming\CustomHtmlHeadContentProvider')
22

3-
@if(setting('app-custom-head') && !request()->routeIs('settings.category'))
3+
@if(!request()->routeIs('settings.category'))
44
<!-- Start: custom user content -->
55
{!! $headContent->forWeb() !!}
66
<!-- End: custom user content -->

tests/Theme/ThemeModuleTest.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Tests\Theme;
44

55
use BookStack\Facades\Theme;
6+
use BookStack\Util\CspService;
67
use Tests\TestCase;
78

89
class ThemeModuleTest extends TestCase
@@ -220,6 +221,23 @@ public function test_module_can_use_theme_view_render_functions()
220221
});
221222
}
222223

224+
public function test_module_can_provide_head_content()
225+
{
226+
$this->usingModuleFolder(function (string $moduleFolderPath) {
227+
mkdir($moduleFolderPath . '/head', 0777, true);
228+
file_put_contents($moduleFolderPath . '/head/hello.html', '<meta name="beans" content="hello"><script>hellofromcustomscript</script>');
229+
230+
$this->refreshApplication();
231+
232+
$cspService = $this->app->make(CspService::class);
233+
$nonce = $cspService->getNonce();
234+
235+
$resp = $this->asAdmin()->get('/');
236+
$resp->assertSee('<meta name="beans" content="hello">', false);
237+
$resp->assertSee('<script nonce="' . $nonce . '">hellofromcustomscript</script>', false);
238+
});
239+
}
240+
223241
protected function usingModuleFolder(callable $callback): void
224242
{
225243
$this->usingThemeFolder(function (string $themeFolder) use ($callback) {

0 commit comments

Comments
 (0)