mirror of
https://github.com/BookStackApp/BookStack.git
synced 2026-07-15 21:31:36 +03:00
@@ -39,7 +39,14 @@ class PageContent
|
||||
public function setNewHTML(string $html, User $updater): void
|
||||
{
|
||||
$html = $this->extractBase64ImagesFromHtml($html, $updater);
|
||||
$this->page->html = $this->formatHtml($html);
|
||||
$html = $this->formatHtml($html);
|
||||
|
||||
$themeResult = Theme::dispatch(ThemeEvents::PAGE_CONTENT_PRE_STORE, $html, $this->page);
|
||||
if (is_string($themeResult)) {
|
||||
$html = $themeResult;
|
||||
}
|
||||
|
||||
$this->page->html = $html;
|
||||
$this->page->text = $this->toPlainText();
|
||||
$this->page->markdown = '';
|
||||
}
|
||||
@@ -52,7 +59,14 @@ class PageContent
|
||||
$markdown = $this->extractBase64ImagesFromMarkdown($markdown, $updater);
|
||||
$this->page->markdown = $markdown;
|
||||
$html = (new MarkdownToHtml($markdown))->convert();
|
||||
$this->page->html = $this->formatHtml($html);
|
||||
$html = $this->formatHtml($html);
|
||||
|
||||
$themeResult = Theme::dispatch(ThemeEvents::PAGE_CONTENT_PRE_STORE, $html, $this->page);
|
||||
if (is_string($themeResult)) {
|
||||
$html = $themeResult;
|
||||
}
|
||||
|
||||
$this->page->html = $html;
|
||||
$this->page->text = $this->toPlainText();
|
||||
}
|
||||
|
||||
@@ -81,7 +95,7 @@ class PageContent
|
||||
|
||||
/**
|
||||
* Convert all inline base64 content to uploaded image files.
|
||||
* Regex is used to locate the start of data-uri definitions then
|
||||
* Regex is used to locate the start of data-uri definitions, then
|
||||
* manual looping over content is done to parse the whole data uri.
|
||||
* Attempting to capture the whole data uri using regex can cause PHP
|
||||
* PCRE limits to be hit with larger, multi-MB, files.
|
||||
@@ -301,7 +315,7 @@ class PageContent
|
||||
$html = $this->page->html ?? '';
|
||||
|
||||
if (empty($html)) {
|
||||
return $html;
|
||||
return $this->handlePostRender('');
|
||||
}
|
||||
|
||||
$doc = new HtmlDocument($html);
|
||||
@@ -322,7 +336,7 @@ class PageContent
|
||||
$cacheKey = $this->getContentCacheKey($doc->getBodyInnerHtml());
|
||||
$cached = cache()->get($cacheKey, null);
|
||||
if ($cached !== null) {
|
||||
return $cached;
|
||||
return $this->handlePostRender($cached);
|
||||
}
|
||||
|
||||
$filterConfig = HtmlContentFilterConfig::fromConfigString(config('app.content_filtering'));
|
||||
@@ -332,7 +346,13 @@ class PageContent
|
||||
$cacheTime = 86400 * 7; // 1 week
|
||||
cache()->put($cacheKey, $filtered, $cacheTime);
|
||||
|
||||
return $filtered;
|
||||
return $this->handlePostRender($filtered);
|
||||
}
|
||||
|
||||
protected function handlePostRender(string $html): string
|
||||
{
|
||||
$themeResult = Theme::dispatch(ThemeEvents::PAGE_CONTENT_POST_RENDER, $html, $this->page);
|
||||
return is_string($themeResult) ? $themeResult : $html;
|
||||
}
|
||||
|
||||
protected function getContentCacheKey(string $html): string
|
||||
|
||||
@@ -111,6 +111,31 @@ class ThemeEvents
|
||||
*/
|
||||
const OIDC_ID_TOKEN_PRE_VALIDATE = 'oidc_id_token_pre_validate';
|
||||
|
||||
/**
|
||||
* Page content post-render event.
|
||||
* Runs after any display rendering of page content, typically when page content is being processed for viewing.
|
||||
* Rendering typically includes parsing of page includes, and content filtering.
|
||||
* Provides the HTML content about to be shown, along with the related page instance.
|
||||
* If the listener returns a string value, that will be used as the HTML content instead.
|
||||
*
|
||||
* @param string $html
|
||||
* @param \BookStack\Entities\Models\Page $page
|
||||
* @return string|null
|
||||
*/
|
||||
const PAGE_CONTENT_POST_RENDER = 'page_content_post_render';
|
||||
|
||||
/**
|
||||
* Page content pre-store event.
|
||||
* Runs just before page HTML is stored in the database, after BookStack's own processing.
|
||||
* Provides the HTML content about to be stored, along with the related page instance.
|
||||
* If the listener returns a string value, that will be used as the HTML content instead.
|
||||
*
|
||||
* @param string $html
|
||||
* @param \BookStack\Entities\Models\Page $page
|
||||
* @return string|null
|
||||
*/
|
||||
const PAGE_CONTENT_PRE_STORE = 'page_content_pre_store';
|
||||
|
||||
/**
|
||||
* Page include parse event.
|
||||
* Runs when a page include tag is being parsed, typically when page content is being processed for viewing.
|
||||
|
||||
@@ -215,6 +215,82 @@ class LogicalThemeEventsTest extends TestCase
|
||||
$this->assertEquals($book->id, $args[1]->id);
|
||||
}
|
||||
|
||||
public function test_page_content_pre_store_fires_on_page_save()
|
||||
{
|
||||
$page = $this->entities->page();
|
||||
|
||||
$args = [];
|
||||
$callback = function (...$eventArgs) use (&$args) {
|
||||
$args = $eventArgs;
|
||||
return '<p>New Content!</p>';
|
||||
};
|
||||
|
||||
Theme::listen(ThemeEvents::PAGE_CONTENT_PRE_STORE, $callback);
|
||||
|
||||
$this->asEditor();
|
||||
$this->entities->updatePage($page, ['name' => 'My cool update page!', 'html' => '<p>Old content!</p>']);
|
||||
|
||||
$this->assertCount(2, $args);
|
||||
$this->assertEquals($page->id, $args[1]->id);
|
||||
$this->assertEquals('<p id="bkmrk-old-content%21">Old content!</p>', $args[0]);
|
||||
|
||||
$newPageHtml = $page->refresh()->html;
|
||||
$this->assertEquals('<p>New Content!</p>', $newPageHtml);
|
||||
}
|
||||
|
||||
public function test_page_content_pre_store_does_not_change_content_if_nothing_returned()
|
||||
{
|
||||
$page = $this->entities->page();
|
||||
Theme::listen(ThemeEvents::PAGE_CONTENT_PRE_STORE, fn() => null);
|
||||
|
||||
$this->asEditor();
|
||||
$this->entities->updatePage($page, ['name' => 'My cool update page!', 'html' => '<p>Old content!</p>']);
|
||||
|
||||
$newPageHtml = $page->refresh()->html;
|
||||
$this->assertEquals('<p id="bkmrk-old-content%21">Old content!</p>', $newPageHtml);
|
||||
}
|
||||
|
||||
public function test_page_content_post_render_fires_on_page_view()
|
||||
{
|
||||
$page = $this->entities->page();
|
||||
$page->html = '<p>Old content!</p>';
|
||||
$page->save();
|
||||
|
||||
$args = [];
|
||||
$callback = function (...$eventArgs) use (&$args) {
|
||||
$args = $eventArgs;
|
||||
return '<p>New postrendercontentforyou!</p>';
|
||||
};
|
||||
|
||||
Theme::listen(ThemeEvents::PAGE_CONTENT_POST_RENDER, $callback);
|
||||
|
||||
$resp = $this->asEditor()->get($page->getUrl());
|
||||
$resp->assertSee('<p>New postrendercontentforyou!</p>', false);
|
||||
|
||||
$this->assertCount(2, $args);
|
||||
$this->assertEquals($page->id, $args[1]->id);
|
||||
$this->assertEquals('<p>Old content!</p>', $args[0]);
|
||||
}
|
||||
|
||||
public function test_page_content_post_render_returns_original_content_if_no_return()
|
||||
{
|
||||
$page = $this->entities->page();
|
||||
$page->html = '<p>Old content!</p>';
|
||||
$page->save();
|
||||
|
||||
$args = [];
|
||||
$callback = function (...$eventArgs) use (&$args) {
|
||||
$args = $eventArgs;
|
||||
};
|
||||
|
||||
Theme::listen(ThemeEvents::PAGE_CONTENT_POST_RENDER, $callback);
|
||||
|
||||
$resp = $this->asEditor()->get($page->getUrl());
|
||||
$resp->assertSee('<p>Old content!</p>', false);
|
||||
|
||||
$this->assertCount(2, $args);
|
||||
}
|
||||
|
||||
public function test_page_include_parse()
|
||||
{
|
||||
/** @var Page $page */
|
||||
|
||||
Reference in New Issue
Block a user