xref: /plugin/bpmnio/inc/link_processor.php (revision 36b712d809a9afeda77eb7dba8abf621818208c9)
1*36b712d8SJaap de Haan<?php
2*36b712d8SJaap de Haan
3*36b712d8SJaap de Haanclass plugin_bpmnio_link_processor
4*36b712d8SJaap de Haan{
5*36b712d8SJaap de Haan    /**
6*36b712d8SJaap de Haan     * @return array{xml: string, links: array<string, array{href: string, target: string}>}
7*36b712d8SJaap de Haan     */
8*36b712d8SJaap de Haan    public static function buildPayload(string $xml): array
9*36b712d8SJaap de Haan    {
10*36b712d8SJaap de Haan        if (trim($xml) === '') {
11*36b712d8SJaap de Haan            return ['xml' => $xml, 'links' => []];
12*36b712d8SJaap de Haan        }
13*36b712d8SJaap de Haan
14*36b712d8SJaap de Haan        $document = new DOMDocument('1.0', 'UTF-8');
15*36b712d8SJaap de Haan        $document->preserveWhiteSpace = true;
16*36b712d8SJaap de Haan        $document->formatOutput = false;
17*36b712d8SJaap de Haan
18*36b712d8SJaap de Haan        $previous = libxml_use_internal_errors(true);
19*36b712d8SJaap de Haan        $loaded = $document->loadXML($xml);
20*36b712d8SJaap de Haan        libxml_clear_errors();
21*36b712d8SJaap de Haan        libxml_use_internal_errors($previous);
22*36b712d8SJaap de Haan
23*36b712d8SJaap de Haan        if (!$loaded) {
24*36b712d8SJaap de Haan            return ['xml' => $xml, 'links' => []];
25*36b712d8SJaap de Haan        }
26*36b712d8SJaap de Haan
27*36b712d8SJaap de Haan        $links = [];
28*36b712d8SJaap de Haan        foreach ($document->getElementsByTagName('*') as $element) {
29*36b712d8SJaap de Haan            if (!$element->hasAttribute('id') || !$element->hasAttribute('name')) {
30*36b712d8SJaap de Haan                continue;
31*36b712d8SJaap de Haan            }
32*36b712d8SJaap de Haan
33*36b712d8SJaap de Haan            $parsedLink = self::parseLinkMarkup($element->getAttribute('name'));
34*36b712d8SJaap de Haan            if ($parsedLink === null) {
35*36b712d8SJaap de Haan                continue;
36*36b712d8SJaap de Haan            }
37*36b712d8SJaap de Haan
38*36b712d8SJaap de Haan            $target = self::resolveTarget($parsedLink['target']);
39*36b712d8SJaap de Haan            if ($target === null || auth_quickaclcheck($target) < AUTH_READ) {
40*36b712d8SJaap de Haan                continue;
41*36b712d8SJaap de Haan            }
42*36b712d8SJaap de Haan
43*36b712d8SJaap de Haan            $elementId = trim($element->getAttribute('id'));
44*36b712d8SJaap de Haan            if ($elementId === '') {
45*36b712d8SJaap de Haan                continue;
46*36b712d8SJaap de Haan            }
47*36b712d8SJaap de Haan
48*36b712d8SJaap de Haan            $label = $parsedLink['label'] !== '' ? $parsedLink['label'] : $target;
49*36b712d8SJaap de Haan
50*36b712d8SJaap de Haan            $element->setAttribute('name', $label);
51*36b712d8SJaap de Haan            $links[$elementId] = [
52*36b712d8SJaap de Haan                'href' => self::buildHref($target),
53*36b712d8SJaap de Haan                'target' => $target,
54*36b712d8SJaap de Haan            ];
55*36b712d8SJaap de Haan        }
56*36b712d8SJaap de Haan
57*36b712d8SJaap de Haan        $renderXml = $document->saveXML();
58*36b712d8SJaap de Haan        if ($renderXml === false) {
59*36b712d8SJaap de Haan            $renderXml = $xml;
60*36b712d8SJaap de Haan        }
61*36b712d8SJaap de Haan
62*36b712d8SJaap de Haan        return ['xml' => $renderXml, 'links' => $links];
63*36b712d8SJaap de Haan    }
64*36b712d8SJaap de Haan
65*36b712d8SJaap de Haan    /**
66*36b712d8SJaap de Haan     * @return array{target: string, label: string}|null
67*36b712d8SJaap de Haan     */
68*36b712d8SJaap de Haan    private static function parseLinkMarkup(string $value): ?array
69*36b712d8SJaap de Haan    {
70*36b712d8SJaap de Haan        $value = trim($value);
71*36b712d8SJaap de Haan        if (!preg_match('/^\[\[([^\]|]+)(?:\|([^\]]*))?\]\]$/', $value, $matches)) {
72*36b712d8SJaap de Haan            return null;
73*36b712d8SJaap de Haan        }
74*36b712d8SJaap de Haan
75*36b712d8SJaap de Haan        $target = trim($matches[1]);
76*36b712d8SJaap de Haan        if ($target === '') {
77*36b712d8SJaap de Haan            return null;
78*36b712d8SJaap de Haan        }
79*36b712d8SJaap de Haan
80*36b712d8SJaap de Haan        return [
81*36b712d8SJaap de Haan            'target' => $target,
82*36b712d8SJaap de Haan            'label' => isset($matches[2]) ? trim($matches[2]) : '',
83*36b712d8SJaap de Haan        ];
84*36b712d8SJaap de Haan    }
85*36b712d8SJaap de Haan
86*36b712d8SJaap de Haan    private static function resolveTarget(string $target): ?string
87*36b712d8SJaap de Haan    {
88*36b712d8SJaap de Haan        global $ID;
89*36b712d8SJaap de Haan
90*36b712d8SJaap de Haan        $target = trim($target);
91*36b712d8SJaap de Haan        if ($target === '' || preg_match('#^[a-z][a-z0-9+.-]*://#i', $target)) {
92*36b712d8SJaap de Haan            return null;
93*36b712d8SJaap de Haan        }
94*36b712d8SJaap de Haan
95*36b712d8SJaap de Haan        if (str_starts_with($target, ':')) {
96*36b712d8SJaap de Haan            return self::normalizeId(trim($target, ':'));
97*36b712d8SJaap de Haan        }
98*36b712d8SJaap de Haan
99*36b712d8SJaap de Haan        $baseNamespace = self::getNamespace((string) $ID);
100*36b712d8SJaap de Haan        if (str_starts_with($target, '.')) {
101*36b712d8SJaap de Haan            $segments = $baseNamespace === '' ? [] : explode(':', $baseNamespace);
102*36b712d8SJaap de Haan            foreach (explode(':', $target) as $segment) {
103*36b712d8SJaap de Haan                $segment = trim($segment);
104*36b712d8SJaap de Haan                if ($segment === '' || $segment === '.') {
105*36b712d8SJaap de Haan                    continue;
106*36b712d8SJaap de Haan                }
107*36b712d8SJaap de Haan                if ($segment === '..') {
108*36b712d8SJaap de Haan                    array_pop($segments);
109*36b712d8SJaap de Haan                    continue;
110*36b712d8SJaap de Haan                }
111*36b712d8SJaap de Haan                $segments[] = $segment;
112*36b712d8SJaap de Haan            }
113*36b712d8SJaap de Haan
114*36b712d8SJaap de Haan            return self::normalizeId(implode(':', $segments));
115*36b712d8SJaap de Haan        }
116*36b712d8SJaap de Haan
117*36b712d8SJaap de Haan        if (str_contains($target, ':')) {
118*36b712d8SJaap de Haan            return self::normalizeId($target);
119*36b712d8SJaap de Haan        }
120*36b712d8SJaap de Haan
121*36b712d8SJaap de Haan        $pageId = $baseNamespace === '' ? $target : $baseNamespace . ':' . $target;
122*36b712d8SJaap de Haan        return self::normalizeId($pageId);
123*36b712d8SJaap de Haan    }
124*36b712d8SJaap de Haan
125*36b712d8SJaap de Haan    private static function buildHref(string $target): string
126*36b712d8SJaap de Haan    {
127*36b712d8SJaap de Haan        return DOKU_BASE . 'doku.php?' . http_build_query(['id' => $target], '', '&', PHP_QUERY_RFC3986);
128*36b712d8SJaap de Haan    }
129*36b712d8SJaap de Haan
130*36b712d8SJaap de Haan    private static function getNamespace(string $pageId): string
131*36b712d8SJaap de Haan    {
132*36b712d8SJaap de Haan        $pos = strrpos($pageId, ':');
133*36b712d8SJaap de Haan        if ($pos === false) {
134*36b712d8SJaap de Haan            return '';
135*36b712d8SJaap de Haan        }
136*36b712d8SJaap de Haan
137*36b712d8SJaap de Haan        return substr($pageId, 0, $pos);
138*36b712d8SJaap de Haan    }
139*36b712d8SJaap de Haan
140*36b712d8SJaap de Haan    private static function normalizeId(string $value): ?string
141*36b712d8SJaap de Haan    {
142*36b712d8SJaap de Haan        $value = trim($value);
143*36b712d8SJaap de Haan        if ($value === '') {
144*36b712d8SJaap de Haan            return null;
145*36b712d8SJaap de Haan        }
146*36b712d8SJaap de Haan
147*36b712d8SJaap de Haan        if (function_exists('cleanID')) {
148*36b712d8SJaap de Haan            $value = cleanID($value);
149*36b712d8SJaap de Haan        }
150*36b712d8SJaap de Haan
151*36b712d8SJaap de Haan        $segments = [];
152*36b712d8SJaap de Haan        foreach (explode(':', $value) as $segment) {
153*36b712d8SJaap de Haan            $segment = trim($segment);
154*36b712d8SJaap de Haan            if ($segment === '' || $segment === '.') {
155*36b712d8SJaap de Haan                continue;
156*36b712d8SJaap de Haan            }
157*36b712d8SJaap de Haan            if ($segment === '..') {
158*36b712d8SJaap de Haan                array_pop($segments);
159*36b712d8SJaap de Haan                continue;
160*36b712d8SJaap de Haan            }
161*36b712d8SJaap de Haan            $segments[] = $segment;
162*36b712d8SJaap de Haan        }
163*36b712d8SJaap de Haan
164*36b712d8SJaap de Haan        if ($segments === []) {
165*36b712d8SJaap de Haan            return null;
166*36b712d8SJaap de Haan        }
167*36b712d8SJaap de Haan
168*36b712d8SJaap de Haan        return implode(':', $segments);
169*36b712d8SJaap de Haan    }
170*36b712d8SJaap de Haan}
171