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