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