mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-08-26 20:22:18 +00:00
parent
1097c61d6d
commit
465d405926
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
use BookStack\Entities\Page;
|
use BookStack\Entities\Page;
|
||||||
use DOMDocument;
|
use DOMDocument;
|
||||||
use DOMElement;
|
|
||||||
use DOMNodeList;
|
use DOMNodeList;
|
||||||
use DOMXPath;
|
use DOMXPath;
|
||||||
|
|
||||||
@ -44,18 +43,24 @@ class PageContent
|
|||||||
$container = $doc->documentElement;
|
$container = $doc->documentElement;
|
||||||
$body = $container->childNodes->item(0);
|
$body = $container->childNodes->item(0);
|
||||||
$childNodes = $body->childNodes;
|
$childNodes = $body->childNodes;
|
||||||
|
$xPath = new DOMXPath($doc);
|
||||||
|
|
||||||
// Set ids on top-level nodes
|
// Set ids on top-level nodes
|
||||||
$idMap = [];
|
$idMap = [];
|
||||||
foreach ($childNodes as $index => $childNode) {
|
foreach ($childNodes as $index => $childNode) {
|
||||||
$this->setUniqueId($childNode, $idMap);
|
[$oldId, $newId] = $this->setUniqueId($childNode, $idMap);
|
||||||
|
if ($newId && $newId !== $oldId) {
|
||||||
|
$this->updateLinks($xPath, '#' . $oldId, '#' . $newId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure no duplicate ids within child items
|
// Ensure no duplicate ids within child items
|
||||||
$xPath = new DOMXPath($doc);
|
|
||||||
$idElems = $xPath->query('//body//*//*[@id]');
|
$idElems = $xPath->query('//body//*//*[@id]');
|
||||||
foreach ($idElems as $domElem) {
|
foreach ($idElems as $domElem) {
|
||||||
$this->setUniqueId($domElem, $idMap);
|
[$oldId, $newId] = $this->setUniqueId($domElem, $idMap);
|
||||||
|
if ($newId && $newId !== $oldId) {
|
||||||
|
$this->updateLinks($xPath, '#' . $oldId, '#' . $newId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate inner html as a string
|
// Generate inner html as a string
|
||||||
@ -68,22 +73,33 @@ class PageContent
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set a unique id on the given DOMElement.
|
* Update the all links to the $old location to instead point to $new.
|
||||||
* A map for existing ID's should be passed in to check for current existence.
|
|
||||||
* @param DOMElement $element
|
|
||||||
* @param array $idMap
|
|
||||||
*/
|
*/
|
||||||
protected function setUniqueId($element, array &$idMap)
|
protected function updateLinks(DOMXPath $xpath, string $old, string $new)
|
||||||
{
|
{
|
||||||
if (get_class($element) !== 'DOMElement') {
|
$old = str_replace('"', '', $old);
|
||||||
return;
|
$matchingLinks = $xpath->query('//body//*//*[@href="'.$old.'"]');
|
||||||
|
foreach ($matchingLinks as $domElem) {
|
||||||
|
$domElem->setAttribute('href', $new);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Overwrite id if not a BookStack custom id
|
/**
|
||||||
|
* Set a unique id on the given DOMElement.
|
||||||
|
* A map for existing ID's should be passed in to check for current existence.
|
||||||
|
* Returns a pair of strings in the format [old_id, new_id]
|
||||||
|
*/
|
||||||
|
protected function setUniqueId(\DOMNode $element, array &$idMap): array
|
||||||
|
{
|
||||||
|
if (get_class($element) !== 'DOMElement') {
|
||||||
|
return ['', ''];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop if there's an existing valid id that has not already been used.
|
||||||
$existingId = $element->getAttribute('id');
|
$existingId = $element->getAttribute('id');
|
||||||
if (strpos($existingId, 'bkmrk') === 0 && !isset($idMap[$existingId])) {
|
if (strpos($existingId, 'bkmrk') === 0 && !isset($idMap[$existingId])) {
|
||||||
$idMap[$existingId] = true;
|
$idMap[$existingId] = true;
|
||||||
return;
|
return [$existingId, $existingId];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create an unique id for the element
|
// Create an unique id for the element
|
||||||
@ -100,6 +116,7 @@ class PageContent
|
|||||||
|
|
||||||
$element->setAttribute('id', $newId);
|
$element->setAttribute('id', $newId);
|
||||||
$idMap[$newId] = true;
|
$idMap[$newId] = true;
|
||||||
|
return [$existingId, $newId];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -262,6 +262,23 @@ class PageContentTest extends TestCase
|
|||||||
$this->assertEquals(substr_count($updatedPage->html, "bkmrk-test\""), 1);
|
$this->assertEquals(substr_count($updatedPage->html, "bkmrk-test\""), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_anchors_referencing_non_bkmrk_ids_rewritten_after_save()
|
||||||
|
{
|
||||||
|
$this->asEditor();
|
||||||
|
$page = Page::first();
|
||||||
|
|
||||||
|
$content = '<h1 id="non-standard-id">test</h1><p><a href="#non-standard-id">link</a></p>';
|
||||||
|
$this->put($page->getUrl(), [
|
||||||
|
'name' => $page->name,
|
||||||
|
'html' => $content,
|
||||||
|
'summary' => ''
|
||||||
|
]);
|
||||||
|
|
||||||
|
$updatedPage = Page::where('id', '=', $page->id)->first();
|
||||||
|
$this->assertStringContainsString('id="bkmrk-test"', $updatedPage->html);
|
||||||
|
$this->assertStringContainsString('href="#bkmrk-test"', $updatedPage->html);
|
||||||
|
}
|
||||||
|
|
||||||
public function test_get_page_nav_sets_correct_properties()
|
public function test_get_page_nav_sets_correct_properties()
|
||||||
{
|
{
|
||||||
$content = '<h1 id="testa">Hello</h1><h2 id="testb">There</h2><h3 id="testc">Donkey</h3>';
|
$content = '<h1 id="testa">Hello</h1><h2 id="testb">There</h2><h3 id="testc">Donkey</h3>';
|
||||||
|
Loading…
Reference in New Issue
Block a user