]> BookStack Code Mirror - bookstack/blob - app/References/ReferenceUpdater.php
Licensing: Added license gen as composer command
[bookstack] / app / References / ReferenceUpdater.php
1 <?php
2
3 namespace BookStack\References;
4
5 use BookStack\Entities\Models\Book;
6 use BookStack\Entities\Models\Entity;
7 use BookStack\Entities\Models\HasHtmlDescription;
8 use BookStack\Entities\Models\Page;
9 use BookStack\Entities\Repos\RevisionRepo;
10 use BookStack\Util\HtmlDocument;
11
12 class ReferenceUpdater
13 {
14     public function __construct(
15         protected ReferenceFetcher $referenceFetcher,
16         protected RevisionRepo $revisionRepo,
17     ) {
18     }
19
20     public function updateEntityReferences(Entity $entity, string $oldLink): void
21     {
22         $references = $this->getReferencesToUpdate($entity);
23         $newLink = $entity->getUrl();
24
25         foreach ($references as $reference) {
26             /** @var Entity $entity */
27             $entity = $reference->from;
28             $this->updateReferencesWithinEntity($entity, $oldLink, $newLink);
29         }
30     }
31
32     /**
33      * @return Reference[]
34      */
35     protected function getReferencesToUpdate(Entity $entity): array
36     {
37         /** @var Reference[] $references */
38         $references = $this->referenceFetcher->getReferencesToEntity($entity)->values()->all();
39
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                 /** @var Reference[] $childRefs */
46                 $childRefs = $this->referenceFetcher->getReferencesToEntity($bookChild)->values()->all();
47                 array_push($references, ...$childRefs);
48             }
49         }
50
51         $deduped = [];
52         foreach ($references as $reference) {
53             $key = $reference->from_id . ':' . $reference->from_type;
54             $deduped[$key] = $reference;
55         }
56
57         return array_values($deduped);
58     }
59
60     protected function updateReferencesWithinEntity(Entity $entity, string $oldLink, string $newLink): void
61     {
62         if ($entity instanceof Page) {
63             $this->updateReferencesWithinPage($entity, $oldLink, $newLink);
64             return;
65         }
66
67         if (in_array(HasHtmlDescription::class, class_uses($entity))) {
68             $this->updateReferencesWithinDescription($entity, $oldLink, $newLink);
69         }
70     }
71
72     protected function updateReferencesWithinDescription(Entity $entity, string $oldLink, string $newLink): void
73     {
74         /** @var HasHtmlDescription&Entity $entity */
75         $entity = (clone $entity)->refresh();
76         $html = $this->updateLinksInHtml($entity->description_html ?: '', $oldLink, $newLink);
77         $entity->description_html = $html;
78         $entity->save();
79     }
80
81     protected function updateReferencesWithinPage(Page $page, string $oldLink, string $newLink): void
82     {
83         $page = (clone $page)->refresh();
84         $html = $this->updateLinksInHtml($page->html, $oldLink, $newLink);
85         $markdown = $this->updateLinksInMarkdown($page->markdown, $oldLink, $newLink);
86
87         $page->html = $html;
88         $page->markdown = $markdown;
89         $page->revision_count++;
90         $page->save();
91
92         $summary = trans('entities.pages_references_update_revision');
93         $this->revisionRepo->storeNewForPage($page, $summary);
94     }
95
96     protected function updateLinksInMarkdown(string $markdown, string $oldLink, string $newLink): string
97     {
98         if (empty($markdown)) {
99             return $markdown;
100         }
101
102         $commonLinkRegex = '/(\[.*?\]\()' . preg_quote($oldLink, '/') . '(.*?\))/i';
103         $markdown = preg_replace($commonLinkRegex, '$1' . $newLink . '$2', $markdown);
104
105         $referenceLinkRegex = '/(\[.*?\]:\s?)' . preg_quote($oldLink, '/') . '(.*?)($|\s)/i';
106         $markdown = preg_replace($referenceLinkRegex, '$1' . $newLink . '$2$3', $markdown);
107
108         return $markdown;
109     }
110
111     protected function updateLinksInHtml(string $html, string $oldLink, string $newLink): string
112     {
113         if (empty($html)) {
114             return $html;
115         }
116
117         $doc = new HtmlDocument($html);
118         $anchors = $doc->queryXPath('//a[@href]');
119
120         /** @var \DOMElement $anchor */
121         foreach ($anchors as $anchor) {
122             $link = $anchor->getAttribute('href');
123             $updated = str_ireplace($oldLink, $newLink, $link);
124             $anchor->setAttribute('href', $updated);
125         }
126
127         return $doc->getBodyInnerHtml();
128     }
129 }