3 namespace BookStack\References;
5 use BookStack\Entities\Models\Book;
6 use BookStack\Entities\Models\Entity;
7 use BookStack\Entities\Models\Page;
8 use BookStack\Entities\Repos\RevisionRepo;
9 use BookStack\Util\HtmlDocument;
11 class ReferenceUpdater
13 public function __construct(
14 protected ReferenceFetcher $referenceFetcher,
15 protected RevisionRepo $revisionRepo
19 public function updateEntityPageReferences(Entity $entity, string $oldLink)
21 $references = $this->getReferencesToUpdate($entity);
22 $newLink = $entity->getUrl();
24 /** @var Reference $reference */
25 foreach ($references as $reference) {
26 /** @var Page $page */
27 $page = $reference->from;
28 $this->updateReferencesWithinPage($page, $oldLink, $newLink);
35 protected function getReferencesToUpdate(Entity $entity): array
37 /** @var Reference[] $references */
38 $references = $this->referenceFetcher->getPageReferencesToEntity($entity)->values()->all();
40 if ($entity instanceof Book) {
41 $pages = $entity->pages()->get(['id']);
42 $chapters = $entity->chapters()->get(['id']);
43 $children = $pages->concat($chapters);
44 foreach ($children as $bookChild) {
45 $childRefs = $this->referenceFetcher->getPageReferencesToEntity($bookChild)->values()->all();
46 array_push($references, ...$childRefs);
51 foreach ($references as $reference) {
52 $key = $reference->from_id . ':' . $reference->from_type;
53 $deduped[$key] = $reference;
56 return array_values($deduped);
59 protected function updateReferencesWithinPage(Page $page, string $oldLink, string $newLink)
61 $page = (clone $page)->refresh();
62 $html = $this->updateLinksInHtml($page->html, $oldLink, $newLink);
63 $markdown = $this->updateLinksInMarkdown($page->markdown, $oldLink, $newLink);
66 $page->markdown = $markdown;
67 $page->revision_count++;
70 $summary = trans('entities.pages_references_update_revision');
71 $this->revisionRepo->storeNewForPage($page, $summary);
74 protected function updateLinksInMarkdown(string $markdown, string $oldLink, string $newLink): string
76 if (empty($markdown)) {
80 $commonLinkRegex = '/(\[.*?\]\()' . preg_quote($oldLink, '/') . '(.*?\))/i';
81 $markdown = preg_replace($commonLinkRegex, '$1' . $newLink . '$2', $markdown);
83 $referenceLinkRegex = '/(\[.*?\]:\s?)' . preg_quote($oldLink, '/') . '(.*?)($|\s)/i';
84 $markdown = preg_replace($referenceLinkRegex, '$1' . $newLink . '$2$3', $markdown);
89 protected function updateLinksInHtml(string $html, string $oldLink, string $newLink): string
95 $doc = new HtmlDocument($html);
96 $anchors = $doc->queryXPath('//a[@href]');
98 /** @var \DOMElement $anchor */
99 foreach ($anchors as $anchor) {
100 $link = $anchor->getAttribute('href');
101 $updated = str_ireplace($oldLink, $newLink, $link);
102 $anchor->setAttribute('href', $updated);
105 return $doc->getBodyInnerHtml();