1<?php
2
3namespace dokuwiki\File;
4
5/**
6 * Resolving relative IDs to absolute ones
7 */
8abstract class Resolver
9{
10    /** @var string context page ID */
11    protected $contextID;
12    /** @var string namespace of context page ID */
13    protected $contextNS;
14
15    /**
16     * @param string $contextID the current pageID that's the context to resolve relative IDs to
17     */
18    public function __construct($contextID)
19    {
20        $this->contextID = $contextID;
21        $this->contextNS = (string)getNS($contextID);
22    }
23
24    /**
25     * Resolves a given ID to be absolute
26     *
27     * @param string $id The ID to resolve
28     * @param string|int|false $rev The revision time to use when resolving
29     * @param bool $isDateAt Is the given revision only a datetime hint not an exact revision?
30     * @return string
31     */
32    public function resolveId($id, $rev = '', $isDateAt = false)
33    {
34        global $conf;
35
36        // some pre cleaning for useslash:
37        if ($conf['useslash']) $id = str_replace('/', ':', $id);
38        // on some systems, semicolons might be used instead of colons:
39        $id = str_replace(';', ':', $id);
40
41        $id = $this->resolvePrefix($id);
42        return $this->resolveRelatives($id);
43    }
44
45    /**
46     * Handle IDs starting with . or ~ and prepend the proper prefix
47     *
48     * @param string $id
49     * @return string
50     */
51    protected function resolvePrefix($id)
52    {
53        if ($id === '') return $id;
54
55        // relative to current page (makes the current page a start page)
56        if ($id[0] === '~') {
57            $id = $this->contextID . ':' . substr($id, 1);
58        }
59
60        // relative to current namespace
61        if ($id[0] === '.') {
62            // normalize initial dots without a colon
63            $id = preg_replace('/^((\.+:)*)(\.+)(?=[^:\.])/', '\1\3:', $id);
64            $id = $this->contextNS . ':' . $id;
65        }
66
67        // auto-relative, because there is a context namespace but no namespace in the ID
68        if ($this->contextID !== '' && strpos($id, ':') === false) {
69            $id = $this->contextNS . ':' . $id;
70        }
71
72        return $id;
73    }
74
75    /**
76     * Handle . and .. within IDs
77     *
78     * @param string $id
79     * @return string
80     */
81    protected function resolveRelatives($id)
82    {
83        $id = rtrim($id, '.'); // trailing dots are invalid
84        if ($id === '') return '';
85        $trail = ($id[-1] === ':') ? ':' : ''; // keep trailing colon
86
87        $result = [];
88        $parts = explode(':', $id);
89
90        foreach ($parts as $dir) {
91            if ($dir === '.') continue;
92            if ($dir === '') continue;
93            if ($dir === '..') {
94                array_pop($result);
95                continue;
96            }
97            $result[] = $dir;
98        }
99
100        $id = implode(':', $result);
101        $id .= $trail;
102
103        return $id;
104    }
105}
106