]> BookStack Code Mirror - bookstack/blob - app/References/CrossLinkParser.php
Fixed linting and failing test issues from dropzone work
[bookstack] / app / References / CrossLinkParser.php
1 <?php
2
3 namespace BookStack\References;
4
5 use BookStack\Model;
6 use BookStack\References\ModelResolvers\BookLinkModelResolver;
7 use BookStack\References\ModelResolvers\BookshelfLinkModelResolver;
8 use BookStack\References\ModelResolvers\ChapterLinkModelResolver;
9 use BookStack\References\ModelResolvers\CrossLinkModelResolver;
10 use BookStack\References\ModelResolvers\PageLinkModelResolver;
11 use BookStack\References\ModelResolvers\PagePermalinkModelResolver;
12 use DOMDocument;
13 use DOMXPath;
14
15 class CrossLinkParser
16 {
17     /**
18      * @var CrossLinkModelResolver[]
19      */
20     protected array $modelResolvers;
21
22     public function __construct(array $modelResolvers)
23     {
24         $this->modelResolvers = $modelResolvers;
25     }
26
27     /**
28      * Extract any found models within the given HTML content.
29      *
30      * @return Model[]
31      */
32     public function extractLinkedModels(string $html): array
33     {
34         $models = [];
35
36         $links = $this->getLinksFromContent($html);
37
38         foreach ($links as $link) {
39             $model = $this->linkToModel($link);
40             if (!is_null($model)) {
41                 $models[get_class($model) . ':' . $model->id] = $model;
42             }
43         }
44
45         return array_values($models);
46     }
47
48     /**
49      * Get a list of href values from the given document.
50      *
51      * @returns string[]
52      */
53     protected function getLinksFromContent(string $html): array
54     {
55         $links = [];
56
57         $html = '<?xml encoding="utf-8" ?><body>' . $html . '</body>';
58         libxml_use_internal_errors(true);
59         $doc = new DOMDocument();
60         $doc->loadHTML($html);
61
62         $xPath = new DOMXPath($doc);
63         $anchors = $xPath->query('//a[@href]');
64
65         /** @var \DOMElement $anchor */
66         foreach ($anchors as $anchor) {
67             $links[] = $anchor->getAttribute('href');
68         }
69
70         return $links;
71     }
72
73     /**
74      * Attempt to resolve the given link to a model using the instance model resolvers.
75      */
76     protected function linkToModel(string $link): ?Model
77     {
78         foreach ($this->modelResolvers as $resolver) {
79             $model = $resolver->resolve($link);
80             if (!is_null($model)) {
81                 return $model;
82             }
83         }
84
85         return null;
86     }
87
88     /**
89      * Create a new instance with a pre-defined set of model resolvers, specifically for the
90      * default set of entities within BookStack.
91      */
92     public static function createWithEntityResolvers(): self
93     {
94         return new self([
95             new PagePermalinkModelResolver(),
96             new PageLinkModelResolver(),
97             new ChapterLinkModelResolver(),
98             new BookLinkModelResolver(),
99             new BookshelfLinkModelResolver(),
100         ]);
101     }
102 }