1<?php 2 3use dokuwiki\File\PageResolver; 4 5/** 6 * DokuWiki Plugin simplenavi (Syntax Component) 7 * 8 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 9 * @author Andreas Gohr <gohr@cosmocode.de> 10 */ 11class syntax_plugin_simplenavi extends DokuWiki_Syntax_Plugin 12{ 13 private $startpages = []; 14 15 /** @inheritdoc */ 16 public function getType() 17 { 18 return 'substition'; 19 } 20 21 /** @inheritdoc */ 22 public function getPType() 23 { 24 return 'block'; 25 } 26 27 /** @inheritdoc */ 28 public function getSort() 29 { 30 return 155; 31 } 32 33 /** @inheritdoc */ 34 public function connectTo($mode) 35 { 36 $this->Lexer->addSpecialPattern('{{simplenavi>[^}]*}}', $mode, 'plugin_simplenavi'); 37 } 38 39 /** @inheritdoc */ 40 public function handle($match, $state, $pos, Doku_Handler $handler) 41 { 42 return explode(' ', substr($match, 13, -2)); 43 } 44 45 /** @inheritdoc */ 46 public function render($format, Doku_Renderer $renderer, $data) 47 { 48 if ($format != 'xhtml') return false; 49 50 global $conf; 51 global $INFO; 52 $renderer->nocache(); 53 54 // first data is namespace, rest is options 55 $ns = array_shift($data); 56 if ($ns && $ns[0] === '.') { 57 // resolve relative to current page 58 $ns = getNS((new PageResolver($INFO['id']))->resolveId("$ns:xxx")); 59 } else { 60 $ns = cleanID($ns); 61 } 62 // convert to path 63 $ns = utf8_encodeFN(str_replace(':', '/', $ns)); 64 65 $items = []; 66 search($items, $conf['datadir'], [$this, 'cbSearch'], ['ns' => $INFO['id']], $ns, 1, 'natural'); 67 if ($this->getConf('sortByTitle')) { 68 $this->sortByTitle($items, "id"); 69 } else { 70 if ($this->getConf('sort') == 'ascii') { 71 uksort($items, [$this, 'pathCompare']); 72 } 73 } 74 75 $class = 'plugin__simplenavi'; 76 if (in_array('filter', $data)) $class .= ' plugin__simplenavi_filter'; 77 78 $renderer->doc .= '<div class="' . $class . '">'; 79 $renderer->doc .= html_buildlist($items, 'idx', [$this, 'cbList'], [$this, 'cbListItem']); 80 $renderer->doc .= '</div>'; 81 82 return true; 83 } 84 85 /** 86 * Create a list openening 87 * 88 * @param array $item 89 * @return string 90 * @see html_buildlist() 91 */ 92 public function cbList($item) 93 { 94 global $INFO; 95 96 if (($item['type'] == 'd' && $item['open']) || $INFO['id'] == $item['id']) { 97 return '<strong>' . html_wikilink(':' . $item['id'], $this->getTitle($item['id'])) . '</strong>'; 98 } else { 99 return html_wikilink(':' . $item['id'], $this->getTitle($item['id'])); 100 } 101 102 } 103 104 /** 105 * Create a list item 106 * 107 * @param array $item 108 * @return string 109 * @see html_buildlist() 110 */ 111 public function cbListItem($item) 112 { 113 if ($item['type'] == "f") { 114 return '<li class="level' . $item['level'] . '">'; 115 } elseif ($item['open']) { 116 return '<li class="open">'; 117 } else { 118 return '<li class="closed">'; 119 } 120 } 121 122 /** 123 * Custom search callback 124 * 125 * @param $data 126 * @param $base 127 * @param $file 128 * @param $type 129 * @param $lvl 130 * @param $opts 131 * @return bool 132 */ 133 public function cbSearch(&$data, $base, $file, $type, $lvl, $opts) 134 { 135 global $conf; 136 $return = true; 137 138 $id = pathID($file); 139 140 if ($type == 'd' && !( 141 preg_match('#^' . $id . '(:|$)#', $opts['ns']) || 142 preg_match('#^' . $id . '(:|$)#', getNS($opts['ns'])) 143 144 )) { 145 //add but don't recurse 146 $return = false; 147 } elseif ($type == 'f' && (!empty($opts['nofiles']) || substr($file, -4) != '.txt')) { 148 //don't add 149 return false; 150 } 151 152 if ($type == 'd' && $conf['sneaky_index'] && auth_quickaclcheck($id . ':') < AUTH_READ) { 153 return false; 154 } 155 156 if ($type == 'd') { 157 // link directories to their start pages 158 $id = "$id:"; 159 $id = (new PageResolver(''))->resolveId($id); 160 $this->startpages[$id] = 1; 161 } elseif (!empty($this->startpages[$id])) { 162 // skip already shown start pages 163 return false; 164 } elseif (noNS($id) == $conf['start']) { 165 // skip the main start page 166 return false; 167 } 168 169 //check hidden 170 if (isHiddenPage($id)) { 171 return false; 172 } 173 174 //check ACL 175 if ($type == 'f' && auth_quickaclcheck($id) < AUTH_READ) { 176 return false; 177 } 178 179 $data[$id] = array( 180 'id' => $id, 181 'type' => $type, 182 'level' => $lvl, 183 'open' => $return, 184 ); 185 return $return; 186 } 187 188 /** 189 * Get the title for the given page ID 190 * 191 * @param string $id 192 * @return string 193 */ 194 protected function getTitle($id) 195 { 196 global $conf; 197 198 if (useHeading('navigation')) { 199 $p = p_get_first_heading($id); 200 } 201 if (!empty($p)) return $p; 202 203 $p = noNS($id); 204 if ($p == $conf['start'] || !$p) { 205 $p = noNS(getNS($id)); 206 if (!$p) { 207 return $conf['start']; 208 } 209 } 210 return $p; 211 } 212 213 /** 214 * Custom comparator to compare IDs 215 * 216 * @param string $a 217 * @param string $b 218 * @return int 219 */ 220 public function pathCompare($a, $b) 221 { 222 global $conf; 223 $a = preg_replace('/' . preg_quote($conf['start'], '/') . '$/', '', $a); 224 $b = preg_replace('/' . preg_quote($conf['start'], '/') . '$/', '', $b); 225 $a = str_replace(':', '/', $a); 226 $b = str_replace(':', '/', $b); 227 228 return strcmp($a, $b); 229 } 230 231 /** 232 * Sort items by title 233 * 234 * @param array[] $array a list of items 235 * @param string $key the key that contains the page ID in each item 236 * @return void 237 */ 238 protected function sortByTitle(&$array, $key) 239 { 240 $sorter = []; 241 $ret = []; 242 reset($array); 243 foreach ($array as $ii => $va) { 244 $sorter[$ii] = $this->getTitle($va[$key]); 245 } 246 if ($this->getConf('sort') == 'ascii') { 247 uksort($sorter, [$this, 'pathCompare']); 248 } else { 249 natcasesort($sorter); 250 } 251 foreach ($sorter as $ii => $va) { 252 $ret[$ii] = $array[$ii]; 253 } 254 $array = $ret; 255 } 256 257} 258