1<?php
2/**
3 * Plugin nspages : Displays nicely a list of the pages of a namespace
4 *
5 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
6 * @author  Guillaume Turri <guillaume.turri@gmail.com>
7 */
8if(!defined('DOKU_INC')) die();
9
10class namespaceFinder {
11    private $wantedNs;
12    private $isSafe;
13
14    /**
15     * Resolves the namespace on construction
16     *
17     * @param string $path the namespace link
18     */
19    function __construct($path){
20        $this->wantedNs = $this->computeWantedNs($path);
21        $this->sanitizeNs();
22    }
23
24    private function computeWantedNs($wantedNS){
25        global $ID;
26
27        // Convert all other separators to colons
28        //  Nb: slashes should be accepted as separator too as they can be a legit separator when the DW conf "useslash" is on. (and anyway slashes are not allowed in page name
29        //  (see https://www.dokuwiki.org/pagename ) so it's the only correct way to deal with it.
30        //  But we don't need to str_replace it because we don't go through "cleanID" (which would handle it when the conf is off) and because we never remove nor escape the slashes
31        //  before they are converted to a FS path
32        $wantedNS = str_replace(';', ':', $wantedNS); // accepted by DW as namespace separator according to https://www.dokuwiki.org/pagename
33
34        $result = '';
35        if($wantedNS == '') {
36            $wantedNS = $this->getCurrentNamespace();
37        }
38        if( $this->isRelativePath($wantedNS) ) {
39            $result = getNS($ID);
40            // normalize initial dots ( ..:..abc -> ..:..:abc )
41            $wantedNS = preg_replace('/^((\.+:)*)(\.+)(?=[^:\.])/', '\1\3:', $wantedNS);
42        } elseif ( $this->isPageRelativePath($wantedNS) ) {
43            $result = $ID;
44            $wantedNS = substr($wantedNS, 1);
45        }
46        $result .= ':'.$wantedNS.':';
47        return $result;
48    }
49
50    private function getCurrentNamespace(){
51        return '.';
52    }
53
54    private function isRelativePath($path){
55        return $path[0] == '.';
56    }
57
58    private function isPageRelativePath($path){
59        return $path[0] == '~';
60    }
61
62    /**
63     * Get rid of '..'.
64     * Therefore, provides a ns which passes the cleanid() function,
65     */
66    private function sanitizeNs(){
67        $ns = explode(':', $this->wantedNs);
68
69        for($i = 0; $i < count($ns); $i++) {
70            if($ns[$i] === '' || $ns[$i] === '.') {
71                array_splice($ns, $i, 1);
72                $i--;
73            } else if($ns[$i] == '..') {
74                if($i == 0) {
75                    //the first can't be '..', to stay inside 'data/pages'
76                    break;
77                } else {
78                    //simplify the path, getting rid of 'ns:..'
79                    array_splice($ns, $i - 1, 2);
80                    $i -= 2;
81                }
82            }
83        }
84
85        $this->isSafe = (count($ns) == 0 || $ns[0] != '..');
86        $this->wantedNs = implode(':', $ns);
87    }
88
89    function getWantedNs(){
90        return $this->wantedNs;
91    }
92
93    function isNsSafe(){
94        return $this->isSafe;
95    }
96
97    function getWantedDirectory(){
98        return $this->namespaceToDirectory($this->wantedNs);
99    }
100
101    static function namespaceToDirectory($ns){
102        return utf8_encodeFN(str_replace(':', '/', $ns));
103    }
104}
105