1<?php 2 3use dokuwiki\Extension\SyntaxPlugin; 4use dokuwiki\File\PageResolver; 5use dokuwiki\TreeBuilder\Node\AbstractNode; 6use dokuwiki\TreeBuilder\Node\WikiNamespace; 7use dokuwiki\TreeBuilder\Node\WikiStartpage; 8use dokuwiki\TreeBuilder\PageTreeBuilder; 9use dokuwiki\TreeBuilder\TreeSort; 10 11/** 12 * DokuWiki Plugin simplenavi (Syntax Component) 13 * 14 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 15 * @author Andreas Gohr <gohr@cosmocode.de> 16 */ 17class syntax_plugin_simplenavi extends SyntaxPlugin 18{ 19 private $startpages = []; 20 21 /** @inheritdoc */ 22 public function getType() 23 { 24 return 'substition'; 25 } 26 27 /** @inheritdoc */ 28 public function getPType() 29 { 30 return 'block'; 31 } 32 33 /** @inheritdoc */ 34 public function getSort() 35 { 36 return 155; 37 } 38 39 /** @inheritdoc */ 40 public function connectTo($mode) 41 { 42 $this->Lexer->addSpecialPattern('{{simplenavi>[^}]*}}', $mode, 'plugin_simplenavi'); 43 } 44 45 /** @inheritdoc */ 46 public function handle($match, $state, $pos, Doku_Handler $handler) 47 { 48 return explode(' ', substr($match, 13, -2)); 49 } 50 51 /** @inheritdoc */ 52 public function render($format, Doku_Renderer $renderer, $data) 53 { 54 if ($format != 'xhtml') return false; 55 56 global $INFO; 57 $renderer->nocache(); 58 59 // first data is namespace, rest is options 60 $ns = array_shift($data); 61 if ($ns && $ns[0] === '.') { 62 // resolve relative to current page 63 $ns = getNS((new PageResolver($INFO['id']))->resolveId("$ns:xxx")); 64 } else { 65 $ns = cleanID($ns); 66 } 67 68 $tree = $this->getTree($ns); 69 $this->renderTree($renderer, $tree->getTop()); 70 71 return true; 72 } 73 74 /** 75 * Create the tree 76 * 77 * @param string $ns 78 * @return PageTreeBuilder 79 */ 80 protected function getTree(string $ns): PageTreeBuilder 81 { 82 $tree = new PageTreeBuilder($ns); 83 $tree->addFlag(PageTreeBuilder::FLAG_NS_AS_STARTPAGE); 84 $tree->addFlag(PageTreeBuilder::FLAG_SELF_TOP); 85 $tree->setRecursionDecision(\Closure::fromCallable([$this, 'treeRecursionDecision'])); 86 $tree->setNodeProcessor(\Closure::fromCallable([$this, 'treeNodeProcessor'])); 87 $tree->generate(); 88 $tree->sort(TreeSort::SORT_BY_NS_FIRST_THEN_TITLE); 89 return $tree; 90 } 91 92 93 /** 94 * Callback for the PageTreeBuilder to decide if we want to recurse into a node 95 * 96 * @param AbstractNode $node 97 * @param int $depth 98 * @return bool 99 */ 100 protected function treeRecursionDecision(AbstractNode $node, int $depth): bool 101 { 102 global $INFO; 103 104 if ($node instanceof WikiStartpage) { 105 $id = $node->getNs(); // use the namespace for startpages 106 } else { 107 $id = $node->getId(); 108 } 109 110 $is_current = $this->isParent($INFO['id'], $id); 111 $node->setProperty('is_current', $is_current); 112 113 // always recurse into the current page path 114 if ($is_current) return true; 115 116 // FIXME for deep peek, we want to recurse until level is reached 117 118 return false; 119 } 120 121 /** 122 * Callback for the PageTreeBuilder to process a node 123 * 124 * @param AbstractNode $node 125 * @return AbstractNode|null 126 */ 127 protected function treeNodeProcessor(AbstractNode $node): ?AbstractNode 128 { 129 $usetitle = $this->getConf('usetitle'); 130 131 $perm = auth_quickaclcheck($node->getId()); 132 $node->setProperty('permission', $perm); 133 $node->setTitle($this->getTitle($node->getId(), $usetitle)); 134 135 136 if ($node->hasChildren()) { 137 // this node has children, we add it to the tree regardless of the permission 138 // permissions are checked again when rendering 139 return $node; 140 } 141 142 if ($perm < AUTH_READ) { 143 // no children, no permission. No need to add it to the tree 144 return null; 145 } 146 147 return $node; 148 } 149 150 151 /** 152 * Example on how to render a TreeBuilder tree 153 * 154 * @param Doku_Renderer $R The current renderer 155 * @param AbstractNode $top The top node of the tree (use getTop() to get it) 156 * @param int $level current nesting level, starting at 1 157 * @return void 158 */ 159 protected function renderTree(Doku_Renderer $R, AbstractNode $top, $level = 1) 160 { 161 $R->listu_open(); 162 foreach ($top->getChildren() as $node) { 163 $isfolder = $node instanceof WikiNamespace; 164 $incurrent = $node->getProperty('is_current', false); 165 166 $R->listitem_open(1, $isfolder); 167 $R->listcontent_open(); 168 if ($incurrent) $R->strong_open(); 169 170 if (((int)$node->getProperty('permission', 0)) < AUTH_READ) { 171 $R->cdata($node->getTitle()); 172 } else { 173 $R->internallink($node->getId(), $node->getTitle(), null, false, 'navigation'); 174 } 175 176 if ($incurrent) $R->strong_close(); 177 $R->listcontent_close(); 178 if ($node->hasChildren()) { 179 $this->renderTree($R, $node, $level + 1); 180 } 181 $R->listitem_close(); 182 } 183 $R->listu_close(); 184 } 185 186 /** 187 * Check if the given parent ID is a parent of the child ID 188 * 189 * @param string $child 190 * @param string $parent 191 * @return bool 192 */ 193 protected function isParent(string $child, string $parent) 194 { 195 $child = explode(':', $child); 196 $parent = explode(':', $parent); 197 return array_slice($child, 0, count($parent)) === $parent; 198 } 199 200 201 /** 202 * Get the title for the given page ID 203 * 204 * @param string $id 205 * @param bool $usetitle - use the first heading as title 206 * @return string 207 */ 208 protected function getTitle($id, $usetitle) 209 { 210 global $conf; 211 212 if ($usetitle) { 213 $p = p_get_first_heading($id); 214 if (!empty($p)) return $p; 215 } 216 217 $p = noNS($id); 218 if ($p == $conf['start'] || !$p) { 219 $p = noNS(getNS($id)); 220 if (!$p) { 221 return $conf['start']; 222 } 223 } 224 return $p; 225 } 226} 227