xref: /plugin/bpmnio/inc/link_processor.php (revision 57e8c51e519401ec595e8597ab561491ffb8cb9e) !
136b712d8SJaap de Haan<?php
236b712d8SJaap de Haan
336b712d8SJaap de Haanclass plugin_bpmnio_link_processor
436b712d8SJaap de Haan{
536b712d8SJaap de Haan    /**
636b712d8SJaap de Haan     * @return array{xml: string, links: array<string, array{href: string, target: string}>}
736b712d8SJaap de Haan     */
836b712d8SJaap de Haan    public static function buildPayload(string $xml): array
936b712d8SJaap de Haan    {
10*57e8c51eSJaap de Haan        $xml = trim($xml);
11*57e8c51eSJaap de Haan        if ($xml === '') {
1236b712d8SJaap de Haan            return ['xml' => $xml, 'links' => []];
1336b712d8SJaap de Haan        }
1436b712d8SJaap de Haan
1536b712d8SJaap de Haan        $document = new DOMDocument('1.0', 'UTF-8');
1636b712d8SJaap de Haan        $document->preserveWhiteSpace = true;
1736b712d8SJaap de Haan        $document->formatOutput = false;
1836b712d8SJaap de Haan
1936b712d8SJaap de Haan        $previous = libxml_use_internal_errors(true);
2036b712d8SJaap de Haan        $loaded = $document->loadXML($xml);
2136b712d8SJaap de Haan        libxml_clear_errors();
2236b712d8SJaap de Haan        libxml_use_internal_errors($previous);
2336b712d8SJaap de Haan
2436b712d8SJaap de Haan        if (!$loaded) {
2536b712d8SJaap de Haan            return ['xml' => $xml, 'links' => []];
2636b712d8SJaap de Haan        }
2736b712d8SJaap de Haan
2836b712d8SJaap de Haan        $links = [];
2936b712d8SJaap de Haan        foreach ($document->getElementsByTagName('*') as $element) {
3036b712d8SJaap de Haan            if (!$element->hasAttribute('id') || !$element->hasAttribute('name')) {
3136b712d8SJaap de Haan                continue;
3236b712d8SJaap de Haan            }
3336b712d8SJaap de Haan
3436b712d8SJaap de Haan            $parsedLink = self::parseLinkMarkup($element->getAttribute('name'));
3536b712d8SJaap de Haan            if ($parsedLink === null) {
3636b712d8SJaap de Haan                continue;
3736b712d8SJaap de Haan            }
3836b712d8SJaap de Haan
3936b712d8SJaap de Haan            $target = self::resolveTarget($parsedLink['target']);
4036b712d8SJaap de Haan            if ($target === null || auth_quickaclcheck($target) < AUTH_READ) {
4136b712d8SJaap de Haan                continue;
4236b712d8SJaap de Haan            }
4336b712d8SJaap de Haan
4436b712d8SJaap de Haan            $elementId = trim($element->getAttribute('id'));
4536b712d8SJaap de Haan            if ($elementId === '') {
4636b712d8SJaap de Haan                continue;
4736b712d8SJaap de Haan            }
4836b712d8SJaap de Haan
4936b712d8SJaap de Haan            $label = $parsedLink['label'] !== '' ? $parsedLink['label'] : $target;
5036b712d8SJaap de Haan
5136b712d8SJaap de Haan            $element->setAttribute('name', $label);
5236b712d8SJaap de Haan            $links[$elementId] = [
5336b712d8SJaap de Haan                'href' => self::buildHref($target),
5436b712d8SJaap de Haan                'target' => $target,
5536b712d8SJaap de Haan            ];
5636b712d8SJaap de Haan        }
5736b712d8SJaap de Haan
5836b712d8SJaap de Haan        $renderXml = $document->saveXML();
5936b712d8SJaap de Haan        if ($renderXml === false) {
6036b712d8SJaap de Haan            $renderXml = $xml;
6136b712d8SJaap de Haan        }
6236b712d8SJaap de Haan
6336b712d8SJaap de Haan        return ['xml' => $renderXml, 'links' => $links];
6436b712d8SJaap de Haan    }
6536b712d8SJaap de Haan
6636b712d8SJaap de Haan    /**
6736b712d8SJaap de Haan     * @return array{target: string, label: string}|null
6836b712d8SJaap de Haan     */
6936b712d8SJaap de Haan    private static function parseLinkMarkup(string $value): ?array
7036b712d8SJaap de Haan    {
7136b712d8SJaap de Haan        $value = trim($value);
7236b712d8SJaap de Haan        if (!preg_match('/^\[\[([^\]|]+)(?:\|([^\]]*))?\]\]$/', $value, $matches)) {
7336b712d8SJaap de Haan            return null;
7436b712d8SJaap de Haan        }
7536b712d8SJaap de Haan
7636b712d8SJaap de Haan        $target = trim($matches[1]);
7736b712d8SJaap de Haan        if ($target === '') {
7836b712d8SJaap de Haan            return null;
7936b712d8SJaap de Haan        }
8036b712d8SJaap de Haan
8136b712d8SJaap de Haan        return [
8236b712d8SJaap de Haan            'target' => $target,
8336b712d8SJaap de Haan            'label' => isset($matches[2]) ? trim($matches[2]) : '',
8436b712d8SJaap de Haan        ];
8536b712d8SJaap de Haan    }
8636b712d8SJaap de Haan
8736b712d8SJaap de Haan    private static function resolveTarget(string $target): ?string
8836b712d8SJaap de Haan    {
8936b712d8SJaap de Haan        global $ID;
9036b712d8SJaap de Haan
9136b712d8SJaap de Haan        $target = trim($target);
9236b712d8SJaap de Haan        if ($target === '' || preg_match('#^[a-z][a-z0-9+.-]*://#i', $target)) {
9336b712d8SJaap de Haan            return null;
9436b712d8SJaap de Haan        }
9536b712d8SJaap de Haan
9636b712d8SJaap de Haan        if (str_starts_with($target, ':')) {
9736b712d8SJaap de Haan            return self::normalizeId(trim($target, ':'));
9836b712d8SJaap de Haan        }
9936b712d8SJaap de Haan
10036b712d8SJaap de Haan        $baseNamespace = self::getNamespace((string) $ID);
10136b712d8SJaap de Haan        if (str_starts_with($target, '.')) {
10236b712d8SJaap de Haan            $segments = $baseNamespace === '' ? [] : explode(':', $baseNamespace);
10336b712d8SJaap de Haan            foreach (explode(':', $target) as $segment) {
10436b712d8SJaap de Haan                $segment = trim($segment);
10536b712d8SJaap de Haan                if ($segment === '' || $segment === '.') {
10636b712d8SJaap de Haan                    continue;
10736b712d8SJaap de Haan                }
10836b712d8SJaap de Haan                if ($segment === '..') {
10936b712d8SJaap de Haan                    array_pop($segments);
11036b712d8SJaap de Haan                    continue;
11136b712d8SJaap de Haan                }
11236b712d8SJaap de Haan                $segments[] = $segment;
11336b712d8SJaap de Haan            }
11436b712d8SJaap de Haan
11536b712d8SJaap de Haan            return self::normalizeId(implode(':', $segments));
11636b712d8SJaap de Haan        }
11736b712d8SJaap de Haan
11836b712d8SJaap de Haan        if (str_contains($target, ':')) {
11936b712d8SJaap de Haan            return self::normalizeId($target);
12036b712d8SJaap de Haan        }
12136b712d8SJaap de Haan
12236b712d8SJaap de Haan        $pageId = $baseNamespace === '' ? $target : $baseNamespace . ':' . $target;
12336b712d8SJaap de Haan        return self::normalizeId($pageId);
12436b712d8SJaap de Haan    }
12536b712d8SJaap de Haan
12636b712d8SJaap de Haan    private static function buildHref(string $target): string
12736b712d8SJaap de Haan    {
12836b712d8SJaap de Haan        return DOKU_BASE . 'doku.php?' . http_build_query(['id' => $target], '', '&', PHP_QUERY_RFC3986);
12936b712d8SJaap de Haan    }
13036b712d8SJaap de Haan
13136b712d8SJaap de Haan    private static function getNamespace(string $pageId): string
13236b712d8SJaap de Haan    {
13336b712d8SJaap de Haan        $pos = strrpos($pageId, ':');
13436b712d8SJaap de Haan        if ($pos === false) {
13536b712d8SJaap de Haan            return '';
13636b712d8SJaap de Haan        }
13736b712d8SJaap de Haan
13836b712d8SJaap de Haan        return substr($pageId, 0, $pos);
13936b712d8SJaap de Haan    }
14036b712d8SJaap de Haan
14136b712d8SJaap de Haan    private static function normalizeId(string $value): ?string
14236b712d8SJaap de Haan    {
14336b712d8SJaap de Haan        $value = trim($value);
14436b712d8SJaap de Haan        if ($value === '') {
14536b712d8SJaap de Haan            return null;
14636b712d8SJaap de Haan        }
14736b712d8SJaap de Haan
14836b712d8SJaap de Haan        if (function_exists('cleanID')) {
14936b712d8SJaap de Haan            $value = cleanID($value);
15036b712d8SJaap de Haan        }
15136b712d8SJaap de Haan
15236b712d8SJaap de Haan        $segments = [];
15336b712d8SJaap de Haan        foreach (explode(':', $value) as $segment) {
15436b712d8SJaap de Haan            $segment = trim($segment);
15536b712d8SJaap de Haan            if ($segment === '' || $segment === '.') {
15636b712d8SJaap de Haan                continue;
15736b712d8SJaap de Haan            }
15836b712d8SJaap de Haan            if ($segment === '..') {
15936b712d8SJaap de Haan                array_pop($segments);
16036b712d8SJaap de Haan                continue;
16136b712d8SJaap de Haan            }
16236b712d8SJaap de Haan            $segments[] = $segment;
16336b712d8SJaap de Haan        }
16436b712d8SJaap de Haan
16536b712d8SJaap de Haan        if ($segments === []) {
16636b712d8SJaap de Haan            return null;
16736b712d8SJaap de Haan        }
16836b712d8SJaap de Haan
16936b712d8SJaap de Haan        return implode(':', $segments);
17036b712d8SJaap de Haan    }
17136b712d8SJaap de Haan}
172