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