22
33use BookStack \Entities \Page ;
44use DOMDocument ;
5- use DOMElement ;
65use DOMNodeList ;
76use DOMXPath ;
87
@@ -44,18 +43,24 @@ protected function formatHtml(string $htmlText): string
4443 $ container = $ doc ->documentElement ;
4544 $ body = $ container ->childNodes ->item (0 );
4645 $ childNodes = $ body ->childNodes ;
46+ $ xPath = new DOMXPath ($ doc );
4747
4848 // Set ids on top-level nodes
4949 $ idMap = [];
5050 foreach ($ childNodes as $ index => $ childNode ) {
51- $ this ->setUniqueId ($ childNode , $ idMap );
51+ [$ oldId , $ newId ] = $ this ->setUniqueId ($ childNode , $ idMap );
52+ if ($ newId && $ newId !== $ oldId ) {
53+ $ this ->updateLinks ($ xPath , '# ' . $ oldId , '# ' . $ newId );
54+ }
5255 }
5356
5457 // Ensure no duplicate ids within child items
55- $ xPath = new DOMXPath ($ doc );
5658 $ idElems = $ xPath ->query ('//body//*//*[@id] ' );
5759 foreach ($ idElems as $ domElem ) {
58- $ this ->setUniqueId ($ domElem , $ idMap );
60+ [$ oldId , $ newId ] = $ this ->setUniqueId ($ domElem , $ idMap );
61+ if ($ newId && $ newId !== $ oldId ) {
62+ $ this ->updateLinks ($ xPath , '# ' . $ oldId , '# ' . $ newId );
63+ }
5964 }
6065
6166 // Generate inner html as a string
@@ -67,23 +72,34 @@ protected function formatHtml(string $htmlText): string
6772 return $ html ;
6873 }
6974
75+ /**
76+ * Update the all links to the $old location to instead point to $new.
77+ */
78+ protected function updateLinks (DOMXPath $ xpath , string $ old , string $ new )
79+ {
80+ $ old = str_replace ('" ' , '' , $ old );
81+ $ matchingLinks = $ xpath ->query ('//body//*//*[@href=" ' .$ old .'"] ' );
82+ foreach ($ matchingLinks as $ domElem ) {
83+ $ domElem ->setAttribute ('href ' , $ new );
84+ }
85+ }
86+
7087 /**
7188 * Set a unique id on the given DOMElement.
7289 * A map for existing ID's should be passed in to check for current existence.
73- * @param DOMElement $element
74- * @param array $idMap
90+ * Returns a pair of strings in the format [old_id, new_id]
7591 */
76- protected function setUniqueId ($ element , array &$ idMap )
92+ protected function setUniqueId (\ DOMNode $ element , array &$ idMap ): array
7793 {
7894 if (get_class ($ element ) !== 'DOMElement ' ) {
79- return ;
95+ return [ '' , '' ] ;
8096 }
8197
82- // Overwrite id if not a BookStack custom id
98+ // Stop if there's an existing valid id that has not already been used.
8399 $ existingId = $ element ->getAttribute ('id ' );
84100 if (strpos ($ existingId , 'bkmrk ' ) === 0 && !isset ($ idMap [$ existingId ])) {
85101 $ idMap [$ existingId ] = true ;
86- return ;
102+ return [ $ existingId , $ existingId ] ;
87103 }
88104
89105 // Create an unique id for the element
@@ -100,6 +116,7 @@ protected function setUniqueId($element, array &$idMap)
100116
101117 $ element ->setAttribute ('id ' , $ newId );
102118 $ idMap [$ newId ] = true ;
119+ return [$ existingId , $ newId ];
103120 }
104121
105122 /**
0 commit comments