Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 21 additions & 12 deletions app/Entities/Tools/PageContent.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
use BookStack\Entities\Models\Page;
use BookStack\Entities\Tools\Markdown\MarkdownToHtml;
use BookStack\Exceptions\ImageUploadException;
use BookStack\Facades\Theme;
use BookStack\Theming\ThemeEvents;
use BookStack\Uploads\ImageRepo;
use BookStack\Uploads\ImageService;
use BookStack\Util\HtmlContentFilter;
Expand Down Expand Up @@ -372,23 +374,30 @@ protected function parsePageIncludes(string $html): string
continue;
}

// Find page and skip this if page not found
// Find page to use, and default replacement to empty string for non-matches.
/** @var ?Page $matchedPage */
$matchedPage = Page::visible()->find($pageId);
if ($matchedPage === null) {
$html = str_replace($fullMatch, '', $html);
continue;
$replacement = '';

if ($matchedPage && count($splitInclude) === 1) {
// If we only have page id, just insert all page html and continue.
$replacement = $matchedPage->html;
} else if ($matchedPage && count($splitInclude) > 1) {
// Otherwise, if our include tag defines a section, load that specific content
$innerContent = $this->fetchSectionOfPage($matchedPage, $splitInclude[1]);
$replacement = trim($innerContent);
}

// If we only have page id, just insert all page html and continue.
if (count($splitInclude) === 1) {
$html = str_replace($fullMatch, $matchedPage->html, $html);
continue;
}
$themeReplacement = Theme::dispatch(
ThemeEvents::PAGE_INCLUDE_PARSE,
$includeId,
$replacement,
clone $this->page,
$matchedPage ? (clone $matchedPage) : null,
);

// Create and load HTML into a document
$innerContent = $this->fetchSectionOfPage($matchedPage, $splitInclude[1]);
$html = str_replace($fullMatch, trim($innerContent), $html);
// Perform the content replacement
$html = str_replace($fullMatch, $themeReplacement ?? $replacement, $html);
}

return $html;
Expand Down
20 changes: 18 additions & 2 deletions app/Theming/ThemeEvents.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace BookStack\Theming;

use BookStack\Entities\Models\Page;

/**
* The ThemeEvents used within BookStack.
*
Expand Down Expand Up @@ -60,15 +62,29 @@ class ThemeEvents

/**
* Commonmark environment configure.
* Provides the commonmark library environment for customization
* before it's used to render markdown content.
* Provides the commonmark library environment for customization before it's used to render markdown content.
* If the listener returns a non-null value, that will be used as an environment instead.
*
* @param \League\CommonMark\ConfigurableEnvironmentInterface $environment
* @returns \League\CommonMark\ConfigurableEnvironmentInterface|null
*/
const COMMONMARK_ENVIRONMENT_CONFIGURE = 'commonmark_environment_configure';

/**
* Page include parse event.
* Runs when a page include tag is being parsed, typically when page content is being processed for viewing.
* Provides the "include tag" reference string, the default BookStack replacement content for the tag,
* the current page being processed, and the page that's being referenced by the include tag.
* The referenced page may be null where the page does not exist or where permissions prevent visibility.
* If the listener returns a non-null value, that will be used as the replacement HTML content instead.
*
* @param string $tagReference
* @param string $replacementHTML
* @param Page $currentPage
* @param ?Page $referencedPage
*/
const PAGE_INCLUDE_PARSE = 'page_include_parse';

/**
* Web before middleware action.
* Runs before the request is handled but after all other middleware apart from those
Expand Down
30 changes: 30 additions & 0 deletions tests/ThemeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,36 @@ public function test_event_activity_logged()
$this->assertEquals($book->id, $args[1]->id);
}

public function test_event_page_include_parse()
{
/** @var Page $page */
/** @var Page $otherPage */
$page = Page::query()->first();
$otherPage = Page::query()->where('id', '!=', $page->id)->first();
$otherPage->html = '<p id="bkmrk-cool">This is a really cool section</p>';
$page->html = "<p>{{@{$otherPage->id}#bkmrk-cool}}</p>";
$page->save();
$otherPage->save();

$args = [];
$callback = function (...$eventArgs) use (&$args) {
$args = $eventArgs;
return '<strong>Big &amp; content replace surprise!</strong>';
};

Theme::listen(ThemeEvents::PAGE_INCLUDE_PARSE, $callback);
$resp = $this->asEditor()->get($page->getUrl());
$this->withHtml($resp)->assertElementContains('.page-content strong', 'Big & content replace surprise!');

$this->assertCount(4, $args);
$this->assertEquals($otherPage->id . '#bkmrk-cool', $args[0]);
$this->assertEquals('This is a really cool section', $args[1]);
$this->assertTrue($args[2] instanceof Page);
$this->assertTrue($args[3] instanceof Page);
$this->assertEquals($page->id, $args[2]->id);
$this->assertEquals($otherPage->id, $args[3]->id);
}

public function test_add_social_driver()
{
Theme::addSocialDriver('catnet', [
Expand Down