Guest User

Untitled

a guest
Nov 17th, 2024
41
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 5.28 KB | None | 0 0
  1. <?php
  2.  
  3. use BookStack\Entities\Models\Page;
  4. use BookStack\Facades\Theme;
  5. use BookStack\Theming\ThemeEvents;
  6. use BookStack\Entities\Tools\PageContent;
  7.  
  8. Theme::listen(ThemeEvents::PAGE_INCLUDE_PARSE, function(string $tagReference, string $replacementHTML, Page $currentPage, ?Page $referencedPage) {
  9. $splitInclude = explode('#', $tagReference, 2);
  10. //don't do anything if we only have the page ID.
  11. if (count($splitInclude) >= 2) {
  12.  
  13. $sectionID = $splitInclude[1];
  14. $sectionsBelowHeaders = (new ExtendedPageContent($currentPage))->fetchSectionsBelowHeader($referencedPage, $sectionID);
  15.  
  16. if ( $sectionsBelowHeaders != '' ){
  17. $replacementHTML = $sectionsBelowHeaders;
  18. }
  19. }
  20.  
  21. return $replacementHTML;
  22. });
  23.  
  24. class ExtendedPageContent extends PageContent {
  25. /**
  26. * Fetch the sub-contents from below a header, and add a "Quelle des Auszugs" blockquote link at the start.
  27. */
  28. public function fetchSectionsBelowHeader(Page $page, string $sectionId): string
  29. {
  30. // Lade den HTML-Inhalt der Seite.
  31. $html = $page->html;
  32. $doc = new DOMDocument();
  33. libxml_use_internal_errors(true); // Fehler unterdrücken, um leere HTML-Tags zu verarbeiten
  34. $doc->loadHTML($html);
  35.  
  36. // Suche das Element mit der angegebenen ID
  37. $matchingElem = $doc->getElementById($sectionId);
  38. if ($matchingElem === null) {
  39. return '';
  40. }
  41.  
  42. // Wenn das Element kein Heading ist, gib nichts zurück.
  43. if (!preg_match("/^h\\d$/i", $matchingElem->nodeName)) {
  44. return '';
  45. }
  46.  
  47. // Hole die URL der Seite
  48. $url = $page->getUrl();
  49.  
  50. // Füge das <hr> vor dem Blockquote hinzu
  51. $hrTagBefore = '<hr>';
  52.  
  53. // Füge den Link zur Originalseite als Blockquote hinzu
  54. $linkHTML = '<blockquote><a href="' . htmlspecialchars($url) . '" target="_blank">Quelle des Auszugs</a></blockquote>';
  55.  
  56. // Hole den Inhalt unterhalb des Headers
  57. $innerContent = '';
  58. $originalHeadingLevel = (int) filter_var($matchingElem->nodeName, FILTER_SANITIZE_NUMBER_INT);
  59. $nextSib = $matchingElem->nextSibling;
  60.  
  61. while ($nextSib) {
  62. // Breche ab, wenn der nächste Bruder ein Heading ist, das gleich oder wichtiger als das Original ist
  63. if (preg_match("/^h\\d$/i", $nextSib->nodeName)) {
  64. $nextSibHeadingLevel = (int) filter_var($nextSib->nodeName, FILTER_SANITIZE_NUMBER_INT);
  65. $preHeadingLevel = $this->getPreHeadingLevel($sectionId);
  66.  
  67. // Berechne das neue Level der Überschrift
  68. $newLevel = $nextSibHeadingLevel - ($originalHeadingLevel - $preHeadingLevel);
  69. $newLevel = max(5, $newLevel); // Verhindere, dass die Überschrift unter H5 sinkt
  70.  
  71. // Setze das neue Tag, je nach Level
  72. $name = ($newLevel <= 5) ? "h" . $newLevel : "strong";
  73. $nextSib = $this->changeTagName($nextSib, $name);
  74.  
  75. // Breche bei einem Heading auf gleichem oder höherem Niveau ab
  76. if ($nextSibHeadingLevel <= $originalHeadingLevel) {
  77. break;
  78. }
  79. }
  80.  
  81. // Füge den HTML-Inhalt des aktuellen Siblings hinzu
  82. $innerContent .= $doc->saveHTML($nextSib);
  83.  
  84. // Gehe zum nächsten Sibling
  85. $nextSib = $nextSib->nextSibling;
  86. }
  87.  
  88. libxml_clear_errors(); // Fehlerbereinigung
  89.  
  90. // Füge das <hr> am Ende des Inhalts hinzu
  91. $hrTagAfter = '<hr>';
  92.  
  93. // Füge das <hr> vor dem Blockquote, den Blockquote-Link und das <hr> nach dem Inhalt zusammen
  94. return $hrTagBefore . $linkHTML . $innerContent . $hrTagAfter;
  95. }
  96.  
  97. /**
  98. * Gets the level of the heading on the currentPage immediately before the insertion point.
  99. * Fallback value is 1 if no headings are found.
  100. *
  101. * @param string $sectionId
  102. *
  103. * @return int
  104. */
  105. protected function getPreHeadingLevel(string $sectionId): int
  106. {
  107. $doc = new DOMDocument();
  108. libxml_use_internal_errors(true);
  109. $doc->loadHTML($this->page->html);
  110.  
  111. $xpath = new DOMXPath($doc);
  112. $query = "//*[contains(text(),'$sectionId')]/preceding::*[self::h1 or self::h2 or self::h3 or self::h4 or self::h5 or self::h6][1]";
  113. $nodeList = $xpath->query($query);
  114.  
  115. // Falls kein vorheriges Heading gefunden wird, setze das Standard-Level auf 1
  116. $preHeadingLevel = 1;
  117. if ($nodeList->length > 0) {
  118. $preHeadingLevel = (int) filter_var($nodeList[0]->nodeName, FILTER_SANITIZE_NUMBER_INT);
  119. }
  120.  
  121. return $preHeadingLevel;
  122. }
  123.  
  124. /**
  125. * Renames a node in a DOM Document.
  126. *
  127. * @param DOMElement $node
  128. * @param string $name
  129. *
  130. * @return DOMNode
  131. */
  132. protected function changeTagName(DOMElement $node, string $name)
  133. {
  134. $childnodes = [];
  135. foreach ($node->childNodes as $child) {
  136. $childnodes[] = $child;
  137. }
  138. $newnode = $node->ownerDocument->createElement($name);
  139. foreach ($childnodes as $child) {
  140. $child2 = $node->ownerDocument->importNode($child, true);
  141. $newnode->appendChild($child2);
  142. }
  143. foreach ($node->attributes as $attrName => $attrNode) {
  144. $attrName = $attrNode->nodeName;
  145. $attrValue = $attrNode->nodeValue;
  146. $newnode->setAttribute($attrName, $attrValue);
  147. }
  148. $node->parentNode->replaceChild($newnode, $node);
  149. return $newnode;
  150. }
  151. }
Advertisement
Add Comment
Please, Sign In to add comment