3 namespace BookStack\References;
5 use BookStack\Entities\Models\Entity;
6 use BookStack\Entities\Models\Page;
7 use BookStack\Entities\Repos\RevisionRepo;
11 class ReferenceUpdater
13 protected ReferenceFetcher $referenceFetcher;
14 protected RevisionRepo $revisionRepo;
16 public function __construct(ReferenceFetcher $referenceFetcher, RevisionRepo $revisionRepo)
18 $this->referenceFetcher = $referenceFetcher;
19 $this->revisionRepo = $revisionRepo;
22 public function updateEntityPageReferences(Entity $entity, string $oldLink)
24 $references = $this->referenceFetcher->getPageReferencesToEntity($entity);
25 $newLink = $entity->getUrl();
27 /** @var Reference $reference */
28 foreach ($references as $reference) {
29 /** @var Page $page */
30 $page = $reference->from;
31 $this->updateReferencesWithinPage($page, $oldLink, $newLink);
35 protected function updateReferencesWithinPage(Page $page, string $oldLink, string $newLink)
37 $page = (clone $page)->refresh();
38 $html = $this->updateLinksInHtml($page->html, $oldLink, $newLink);
39 $markdown = $this->updateLinksInMarkdown($page->markdown, $oldLink, $newLink);
42 $page->markdown = $markdown;
43 $page->revision_count++;
46 $summary = trans('entities.pages_references_update_revision');
47 $this->revisionRepo->storeNewForPage($page, $summary);
50 protected function updateLinksInMarkdown(string $markdown, string $oldLink, string $newLink): string
52 if (empty($markdown)) {
56 $commonLinkRegex = '/(\[.*?\]\()' . preg_quote($oldLink, '/') . '(.*?\))/i';
57 $markdown = preg_replace($commonLinkRegex, '$1' . $newLink . '$2', $markdown);
59 $referenceLinkRegex = '/(\[.*?\]:\s?)' . preg_quote($oldLink, '/') . '(.*?)($|\s)/i';
60 $markdown = preg_replace($referenceLinkRegex, '$1' . $newLink . '$2$3', $markdown);
65 protected function updateLinksInHtml(string $html, string $oldLink, string $newLink): string
71 $html = '<body>' . $html . '</body>';
72 libxml_use_internal_errors(true);
73 $doc = new DOMDocument();
74 $doc->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'));
76 $xPath = new DOMXPath($doc);
77 $anchors = $xPath->query('//a[@href]');
79 /** @var \DOMElement $anchor */
80 foreach ($anchors as $anchor) {
81 $link = $anchor->getAttribute('href');
82 $updated = str_ireplace($oldLink, $newLink, $link);
83 $anchor->setAttribute('href', $updated);
87 $topElems = $doc->documentElement->childNodes->item(0)->childNodes;
88 foreach ($topElems as $child) {
89 $html .= $doc->saveHTML($child);