1<?php	//	Last Change 2023-04-19
2
3if(!defined('DOKU_INC')) {
4    define ('DOKU_INC', realpath(dirname(__FILE__).'/../../').'/');
5}
6if(!defined('DOKU_PLUGIN')) {
7    define ('DOKU_PLUGIN', DOKU_INC.'lib/plugins/');
8}
9
10require_once (DOKU_PLUGIN.'syntax.php');
11require_once (DOKU_INC.'inc/search.php');
12require_once (DOKU_INC.'inc/pageutils.php');
13
14class syntax_plugin_subpages extends DokuWiki_Syntax_Plugin {
15    var $debug = false;
16    var $pages = Array();
17    var $subpages = Array();
18    var $start = "start";
19    var $useheading = 0;
20    var $datadir = "";
21
22    /**
23     * Constructor
24     */
25    function __construct() {
26        global $conf;
27
28        // if($conf ["allowdebug"] == 1)
29            // $this->debug = true;
30
31        $this->start		= $conf['start'];
32        $this->useheading	= $conf['useheading'];
33        $this->datadir		= $conf['datadir'];
34		// $this->style   		= $this->getConf("style");
35    }
36
37    /**
38     * return some info
39     */
40    function getInfo() {
41    }
42
43    /**
44     * What kind of syntax are we?
45     */
46    function getType() {
47        return "substition";
48    }
49
50    /**
51     * Just before build in links
52     */
53    function getSort() {
54        return 299;
55    }
56
57    /**
58     * Register the ~~SUBPAGES~~ verb
59     */
60    function connectTo($mode) {
61        $this->Lexer->addSpecialPattern('~~SUBPAGES~~', $mode, 'plugin_subpages');
62    }
63
64    /**
65     * Handle the match
66     */
67    function handle($match, $state, $pos, Doku_Handler $handler) {
68        return preg_replace("%~~SUBPAGES~~%", "\\2", $match);
69    }
70
71    /**
72     * Create output
73     */
74    function render($mode, Doku_Renderer $renderer, $data) {
75        $this->rdr         =& $renderer;
76        $this->rdrMode     = $mode;
77
78		$data = $this->_listSubpages($data);
79
80		$this->_put($data);
81
82        return true;
83    }
84
85     /**
86      * Put a debug message on screen...
87      */
88    function _showDebugMsg($msg) {
89        if(!$this->debug) return;
90
91        if(is_array($msg)) {
92            foreach($msg as $index => $m) {
93                $this->_showDebugMsg("Array [$index]: ".$m);
94            }
95            return;
96        }
97
98        echo DOKU_LF."<span style='color:red;'>SUBPAGES_PLUGIN: ".hsc($msg)."</span><br>";
99    }
100
101    /**
102     * Write data to the output stream
103     */
104    function _put($data) {
105        if($data == NULL || $data == '')
106            return;
107
108        switch($this->rdrMode) {
109            case 'xhtml':
110                $this->rdr->doc .= $data;
111                break;
112            case 'latex':
113                $this->rdr->put($data);
114                break;
115        }
116    }
117
118    /**
119     * Get the namespace of the parent directory
120     * (always prefixed and postfixed with a colon, root is ':')
121     */
122    function _getParentNS($id) {
123        // global $ID ;
124        $curNS = getNS($id);
125
126        if($curNS == '') return ':';
127
128        if(substr($curNS, 0, 1) != ':') {
129            $curNS = ':'.$curNS;
130        }
131
132        return $curNS.':';
133    }
134
135    /**
136     * Create a fully qualified namespace from the specified one.
137     * The second parameter must be true when the given namespace
138     * is never a page id. In that case, the returned namespace
139     * always ends with a colon.
140     */
141    function _getFqidOfNS($ns, $mustBeNSnoPage) {
142        global $ID;
143
144        if(substr($ns, 0, 2) == '.:') {
145            $ns = ':'.getNS($ID).substr($ns, 1);
146        } elseif(substr($ns, 0, 3) == '..:') {
147            $ns = $this->_getParentNS($ID).substr($ns, 3);
148        } elseif($ns == '..') {
149            $ns = $this->_getParentNS($ID);
150        } elseif(substr($ns, 0, 1) == ':') {
151        } elseif($ns == '.' || $ns == '*') {
152            $ns = ':'.getNS($ID);
153        } else {
154            $ns = ':'.getNS($ID).':'.$ns;
155        }
156
157        if($mustBeNSnoPage && substr($ns, -1) <> ':') $ns .= ':';
158
159        return $ns;
160    }
161
162    /**
163     * Convert namespace to its path
164     */
165    function _getPathOfNS($ns) {
166        if($ns == ':' || $ns == '') return $this->datadir;
167        $ns = trim($ns, ':');
168        $path = $this->datadir.'/'.utf8_encodeFN(str_replace(':', '/', $ns));
169        return $path;
170    }
171
172    /**
173     * Get title of page
174     */
175	function _getPageTitle($fqid) {
176		if ($this->useheading == 1) {
177            $p = p_get_first_heading($fqid);
178        }
179        if(!empty($p)) return $p;
180
181        $p = noNS($fqid);
182        if ($p == $this->start || $p == false) {
183            $p = noNS(getNS($fqid));
184            if ($p == false) {
185                return $this->start;
186            }
187        }
188        return $p;
189    }
190
191    /**
192     * Receive the search result and add it to pages-array
193     */
194	function _searchCallback(&$data, $base, $file, $type, $level, $opts) {
195		$this->_showDebugMsg('Entering '.__FUNCTION__);
196		global $ID;
197
198		if ($type == 'd') {
199			// subdirectory
200			$chkfile = $file.'/'.$this->start.'.txt';
201			$pgid = pathID($chkfile);
202			$fqid = $opts['ns'].$pgid;
203			if (auth_quickaclcheck($pgid) > AUTH_NONE) {
204				$data['title']	= $this->_getPageTitle($fqid);
205				$data['linkid']	= $fqid;
206				$data['type']	= $type;
207				$data['level']	= $level;
208			}
209			else
210				return false; // no access
211		}
212		else {
213			// page in current directory
214			$pgid = pathID($file);
215			$fqid = $opts['ns'].$pgid;
216			if ($fqid != ':'.$ID) {
217				if (auth_quickaclcheck($pgid) > AUTH_NONE) {
218					$data['title']	= $this->_getPageTitle($fqid);
219					$data['linkid']	= $fqid;
220					$data['type']	= $type;
221					$data['level']	= $level;
222				}
223				else
224					return false; // no access
225			}
226			else
227				return false; // don't display current page
228		}
229
230		array_push($this->pages, $data);
231		$this->_showDebugMsg('Leaving '.__FUNCTION__);
232	}
233
234    /**
235     * Search all pages below the current one
236     */
237	function _listSubpages($data) {
238		$this->_showDebugMsg('Entering '.__FUNCTION__);
239
240		$ns    = '.';
241        $ns    = $this->_getFqidOfNS($ns, true);
242        $path  = $this->_getPathOfNS($ns);
243
244        //
245        // Search the directory $dir, only if the pages array
246        // is empty, since we can pass here several times (xhtml, latex).
247        //
248        $this->_showDebugMsg("Namespace is  $ns");
249        $this->_showDebugMsg("Search dir is $path");
250
251        if (count($this->pages) == 0) {
252        	// https://xref.dokuwiki.org/reference/dokuwiki/////nav.html?inc/Search/index.html
253            $dummy = array();
254			search($dummy, $path, array($this, '_searchCallback'), array('ns' => $ns),'',1,'natural');
255
256			//echo highlight_string(print_r($this->pages, true));
257			//asort($this->pages);
258			sort($this->pages);
259			//echo highlight_string(print_r($this->pages, true));
260        }
261        $count = count($this->pages);
262
263        //$this->_showDebugMsg("Found ".$count." pages!");
264        //foreach ($this->pages as $page) {
265        //	$this->_showDebugMsg($page);
266        //}
267
268		if (count($this->pages) > 0) {
269			$ret  = '<ul class="subpages">';
270	        foreach ($this->pages as $page) {
271	        	//$this->_showDebugMsg($page);
272        		$ret .= '<li>'.html_wikilink($page['linkid'], $page['title']).'</li>';
273	        }
274			$ret .= '</ul>';
275		}
276		else
277			$ret = '';
278
279		$this->_showDebugMsg('Leaving '.__FUNCTION__);
280		return $ret;
281	}
282
283} // syntax_plugin_subpages
284