2021-09-04 13:57:04 +01:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace BookStack\Theming;
|
|
|
|
|
|
|
|
|
|
use BookStack\Util\CspService;
|
|
|
|
|
use BookStack\Util\HtmlContentFilter;
|
2026-02-13 14:14:28 +00:00
|
|
|
use BookStack\Util\HtmlContentFilterConfig;
|
2021-09-04 13:57:04 +01:00
|
|
|
use BookStack\Util\HtmlNonceApplicator;
|
|
|
|
|
use Illuminate\Contracts\Cache\Repository as Cache;
|
|
|
|
|
|
|
|
|
|
class CustomHtmlHeadContentProvider
|
|
|
|
|
{
|
2026-02-13 14:14:28 +00:00
|
|
|
public function __construct(
|
|
|
|
|
protected CspService $cspService,
|
2026-03-08 10:26:00 +00:00
|
|
|
protected Cache $cache,
|
|
|
|
|
protected ThemeService $themeService,
|
2026-02-13 14:14:28 +00:00
|
|
|
) {
|
2021-09-04 13:57:04 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Fetch our custom HTML head content prepared for use on web pages.
|
|
|
|
|
* Content has a nonce applied for CSP.
|
|
|
|
|
*/
|
|
|
|
|
public function forWeb(): string
|
|
|
|
|
{
|
|
|
|
|
$content = $this->getSourceContent();
|
2026-03-08 10:26:00 +00:00
|
|
|
$hash = md5($content) . ':' . $this->themeService->getModulesHash();
|
2021-09-06 22:19:06 +01:00
|
|
|
$html = $this->cache->remember('custom-head-web:' . $hash, 86400, function () use ($content) {
|
2026-03-08 10:26:00 +00:00
|
|
|
$content .= "\n" . $this->getModuleHeadContent();
|
2021-09-04 13:57:04 +01:00
|
|
|
return HtmlNonceApplicator::prepare($content);
|
|
|
|
|
});
|
2021-09-06 22:19:06 +01:00
|
|
|
|
2021-09-04 13:57:04 +01:00
|
|
|
return HtmlNonceApplicator::apply($html, $this->cspService->getNonce());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Fetch our custom HTML head content prepared for use in export formats.
|
|
|
|
|
* Scripts are stripped to avoid potential issues.
|
|
|
|
|
*/
|
|
|
|
|
public function forExport(): string
|
|
|
|
|
{
|
|
|
|
|
$content = $this->getSourceContent();
|
|
|
|
|
$hash = md5($content);
|
2021-09-06 22:19:06 +01:00
|
|
|
|
|
|
|
|
return $this->cache->remember('custom-head-export:' . $hash, 86400, function () use ($content) {
|
2026-02-15 18:44:14 +00:00
|
|
|
$config = new HtmlContentFilterConfig(filterOutNonContentElements: false, useAllowListFilter: false);
|
2026-02-13 14:14:28 +00:00
|
|
|
return (new HtmlContentFilter($config))->filterString($content);
|
2021-09-04 13:57:04 +01:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get the original custom head content to use.
|
|
|
|
|
*/
|
|
|
|
|
protected function getSourceContent(): string
|
|
|
|
|
{
|
|
|
|
|
return setting('app-custom-head', '');
|
|
|
|
|
}
|
2026-03-08 10:26:00 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get any custom head content from installed modules.
|
|
|
|
|
*/
|
|
|
|
|
protected function getModuleHeadContent(): string
|
|
|
|
|
{
|
|
|
|
|
$content = '';
|
|
|
|
|
foreach ($this->themeService->getModules() as $module) {
|
|
|
|
|
$headContentPath = $module->path('head');
|
|
|
|
|
if (file_exists($headContentPath) && is_dir($headContentPath)) {
|
|
|
|
|
$htmlFiles = glob($headContentPath . '/*.html');
|
|
|
|
|
foreach ($htmlFiles as $file) {
|
|
|
|
|
$content .= file_get_contents($file);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $content;
|
|
|
|
|
}
|
2021-09-06 22:19:06 +01:00
|
|
|
}
|