11169a1acSAndreas Gohr<?php 2d8ce5486SAndreas Gohr 3d418c031SAndreas Gohruse dokuwiki\Extension\SyntaxPlugin; 4d8ce5486SAndreas Gohruse dokuwiki\File\PageResolver; 5*ba73b2c8SAndreas Gohruse dokuwiki\TreeBuilder\Node\AbstractNode; 6*ba73b2c8SAndreas Gohruse dokuwiki\TreeBuilder\Node\WikiNamespace; 7*ba73b2c8SAndreas Gohruse dokuwiki\TreeBuilder\Node\WikiStartpage; 8*ba73b2c8SAndreas Gohruse dokuwiki\TreeBuilder\PageTreeBuilder; 9*ba73b2c8SAndreas Gohruse dokuwiki\TreeBuilder\TreeSort; 10d8ce5486SAndreas Gohr 111169a1acSAndreas Gohr/** 121169a1acSAndreas Gohr * DokuWiki Plugin simplenavi (Syntax Component) 131169a1acSAndreas Gohr * 141169a1acSAndreas Gohr * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 151169a1acSAndreas Gohr * @author Andreas Gohr <gohr@cosmocode.de> 161169a1acSAndreas Gohr */ 17d418c031SAndreas Gohrclass syntax_plugin_simplenavi extends SyntaxPlugin 18d8ce5486SAndreas Gohr{ 19d8ce5486SAndreas Gohr private $startpages = []; 201169a1acSAndreas Gohr 21d8ce5486SAndreas Gohr /** @inheritdoc */ 22d8ce5486SAndreas Gohr public function getType() 23d8ce5486SAndreas Gohr { 241169a1acSAndreas Gohr return 'substition'; 251169a1acSAndreas Gohr } 261169a1acSAndreas Gohr 27d8ce5486SAndreas Gohr /** @inheritdoc */ 28d8ce5486SAndreas Gohr public function getPType() 29d8ce5486SAndreas Gohr { 301169a1acSAndreas Gohr return 'block'; 311169a1acSAndreas Gohr } 321169a1acSAndreas Gohr 33d8ce5486SAndreas Gohr /** @inheritdoc */ 34d8ce5486SAndreas Gohr public function getSort() 35d8ce5486SAndreas Gohr { 361169a1acSAndreas Gohr return 155; 371169a1acSAndreas Gohr } 381169a1acSAndreas Gohr 39d8ce5486SAndreas Gohr /** @inheritdoc */ 40d8ce5486SAndreas Gohr public function connectTo($mode) 41d8ce5486SAndreas Gohr { 421169a1acSAndreas Gohr $this->Lexer->addSpecialPattern('{{simplenavi>[^}]*}}', $mode, 'plugin_simplenavi'); 431169a1acSAndreas Gohr } 441169a1acSAndreas Gohr 45d8ce5486SAndreas Gohr /** @inheritdoc */ 46d8ce5486SAndreas Gohr public function handle($match, $state, $pos, Doku_Handler $handler) 47d8ce5486SAndreas Gohr { 485655937aSAndreas Gohr return explode(' ', substr($match, 13, -2)); 491169a1acSAndreas Gohr } 501169a1acSAndreas Gohr 51d8ce5486SAndreas Gohr /** @inheritdoc */ 52d8ce5486SAndreas Gohr public function render($format, Doku_Renderer $renderer, $data) 53d8ce5486SAndreas Gohr { 54d8ce5486SAndreas Gohr if ($format != 'xhtml') return false; 551169a1acSAndreas Gohr 561169a1acSAndreas Gohr global $INFO; 57b3e02951SAndreas Gohr $renderer->nocache(); 581169a1acSAndreas Gohr 59b3e02951SAndreas Gohr // first data is namespace, rest is options 605655937aSAndreas Gohr $ns = array_shift($data); 615655937aSAndreas Gohr if ($ns && $ns[0] === '.') { 625655937aSAndreas Gohr // resolve relative to current page 635655937aSAndreas Gohr $ns = getNS((new PageResolver($INFO['id']))->resolveId("$ns:xxx")); 645655937aSAndreas Gohr } else { 655655937aSAndreas Gohr $ns = cleanID($ns); 665655937aSAndreas Gohr } 67b3e02951SAndreas Gohr 68*ba73b2c8SAndreas Gohr $tree = $this->getTree($ns); 69*ba73b2c8SAndreas Gohr $this->renderTree($renderer, $tree->getTop()); 701169a1acSAndreas Gohr 711169a1acSAndreas Gohr return true; 721169a1acSAndreas Gohr } 731169a1acSAndreas Gohr 74d8ce5486SAndreas Gohr /** 75*ba73b2c8SAndreas Gohr * Create the tree 76e75a33bfSAndreas Gohr * 77*ba73b2c8SAndreas Gohr * @param string $ns 78*ba73b2c8SAndreas Gohr * @return PageTreeBuilder 79e75a33bfSAndreas Gohr */ 80*ba73b2c8SAndreas Gohr protected function getTree(string $ns): PageTreeBuilder 81e75a33bfSAndreas Gohr { 82*ba73b2c8SAndreas Gohr $tree = new PageTreeBuilder($ns); 83*ba73b2c8SAndreas Gohr $tree->addFlag(PageTreeBuilder::FLAG_NS_AS_STARTPAGE); 84*ba73b2c8SAndreas Gohr $tree->addFlag(PageTreeBuilder::FLAG_SELF_TOP); 85*ba73b2c8SAndreas Gohr $tree->setRecursionDecision(\Closure::fromCallable([$this, 'treeRecursionDecision'])); 86*ba73b2c8SAndreas Gohr $tree->setNodeProcessor(\Closure::fromCallable([$this, 'treeNodeProcessor'])); 87*ba73b2c8SAndreas Gohr $tree->generate(); 88*ba73b2c8SAndreas Gohr $tree->sort(TreeSort::SORT_BY_NS_FIRST_THEN_TITLE); 89*ba73b2c8SAndreas Gohr return $tree; 90e75a33bfSAndreas Gohr } 91e75a33bfSAndreas Gohr 92e75a33bfSAndreas Gohr 93e75a33bfSAndreas Gohr /** 94*ba73b2c8SAndreas Gohr * Callback for the PageTreeBuilder to decide if we want to recurse into a node 95d8ce5486SAndreas Gohr * 96*ba73b2c8SAndreas Gohr * @param AbstractNode $node 97*ba73b2c8SAndreas Gohr * @param int $depth 98*ba73b2c8SAndreas Gohr * @return bool 99d8ce5486SAndreas Gohr */ 100*ba73b2c8SAndreas Gohr protected function treeRecursionDecision(AbstractNode $node, int $depth): bool 101d8ce5486SAndreas Gohr { 102492ddc4eSAndreas Gohr global $INFO; 103492ddc4eSAndreas Gohr 104*ba73b2c8SAndreas Gohr if ($node instanceof WikiStartpage) { 105*ba73b2c8SAndreas Gohr $id = $node->getNs(); // use the namespace for startpages 106492ddc4eSAndreas Gohr } else { 107*ba73b2c8SAndreas Gohr $id = $node->getId(); 108492ddc4eSAndreas Gohr } 109*ba73b2c8SAndreas Gohr 110*ba73b2c8SAndreas Gohr $is_current = $this->isParent($INFO['id'], $id); 111*ba73b2c8SAndreas Gohr $node->setProperty('is_current', $is_current); 112*ba73b2c8SAndreas Gohr 113*ba73b2c8SAndreas Gohr // always recurse into the current page path 114*ba73b2c8SAndreas Gohr if ($is_current) return true; 115*ba73b2c8SAndreas Gohr 116*ba73b2c8SAndreas Gohr // FIXME for deep peek, we want to recurse until level is reached 117*ba73b2c8SAndreas Gohr 118*ba73b2c8SAndreas Gohr return false; 1191169a1acSAndreas Gohr } 1201169a1acSAndreas Gohr 121d8ce5486SAndreas Gohr /** 122*ba73b2c8SAndreas Gohr * Callback for the PageTreeBuilder to process a node 123d8ce5486SAndreas Gohr * 124*ba73b2c8SAndreas Gohr * @param AbstractNode $node 125*ba73b2c8SAndreas Gohr * @return AbstractNode|null 126d8ce5486SAndreas Gohr */ 127*ba73b2c8SAndreas Gohr protected function treeNodeProcessor(AbstractNode $node): ?AbstractNode 128d8ce5486SAndreas Gohr { 129*ba73b2c8SAndreas Gohr $usetitle = $this->getConf('usetitle'); 130*ba73b2c8SAndreas Gohr 131*ba73b2c8SAndreas Gohr $perm = auth_quickaclcheck($node->getId()); 132*ba73b2c8SAndreas Gohr $node->setProperty('permission', $perm); 133*ba73b2c8SAndreas Gohr $node->setTitle($this->getTitle($node->getId(), $usetitle)); 134*ba73b2c8SAndreas Gohr 135*ba73b2c8SAndreas Gohr 136*ba73b2c8SAndreas Gohr if ($node->hasChildren()) { 137*ba73b2c8SAndreas Gohr // this node has children, we add it to the tree regardless of the permission 138*ba73b2c8SAndreas Gohr // permissions are checked again when rendering 139*ba73b2c8SAndreas Gohr return $node; 1401169a1acSAndreas Gohr } 141*ba73b2c8SAndreas Gohr 142*ba73b2c8SAndreas Gohr if ($perm < AUTH_READ) { 143*ba73b2c8SAndreas Gohr // no children, no permission. No need to add it to the tree 144*ba73b2c8SAndreas Gohr return null; 145*ba73b2c8SAndreas Gohr } 146*ba73b2c8SAndreas Gohr 147*ba73b2c8SAndreas Gohr return $node; 148*ba73b2c8SAndreas Gohr } 149*ba73b2c8SAndreas Gohr 150*ba73b2c8SAndreas Gohr 151*ba73b2c8SAndreas Gohr /** 152*ba73b2c8SAndreas Gohr * Example on how to render a TreeBuilder tree 153*ba73b2c8SAndreas Gohr * 154*ba73b2c8SAndreas Gohr * @param Doku_Renderer $R The current renderer 155*ba73b2c8SAndreas Gohr * @param AbstractNode $top The top node of the tree (use getTop() to get it) 156*ba73b2c8SAndreas Gohr * @param int $level current nesting level, starting at 1 157*ba73b2c8SAndreas Gohr * @return void 158*ba73b2c8SAndreas Gohr */ 159*ba73b2c8SAndreas Gohr protected function renderTree(Doku_Renderer $R, AbstractNode $top, $level = 1) 160*ba73b2c8SAndreas Gohr { 161*ba73b2c8SAndreas Gohr $R->listu_open(); 162*ba73b2c8SAndreas Gohr foreach ($top->getChildren() as $node) { 163*ba73b2c8SAndreas Gohr $isfolder = $node instanceof WikiNamespace; 164*ba73b2c8SAndreas Gohr $incurrent = $node->getProperty('is_current', false); 165*ba73b2c8SAndreas Gohr 166*ba73b2c8SAndreas Gohr $R->listitem_open(1, $isfolder); 167*ba73b2c8SAndreas Gohr $R->listcontent_open(); 168*ba73b2c8SAndreas Gohr if ($incurrent) $R->strong_open(); 169*ba73b2c8SAndreas Gohr 170*ba73b2c8SAndreas Gohr if (((int)$node->getProperty('permission', 0)) < AUTH_READ) { 171*ba73b2c8SAndreas Gohr $R->cdata($node->getTitle()); 172*ba73b2c8SAndreas Gohr } else { 173*ba73b2c8SAndreas Gohr $R->internallink($node->getId(), $node->getTitle(), null, false, 'navigation'); 174*ba73b2c8SAndreas Gohr } 175*ba73b2c8SAndreas Gohr 176*ba73b2c8SAndreas Gohr if ($incurrent) $R->strong_close(); 177*ba73b2c8SAndreas Gohr $R->listcontent_close(); 178*ba73b2c8SAndreas Gohr if ($node->hasChildren()) { 179*ba73b2c8SAndreas Gohr $this->renderTree($R, $node, $level + 1); 180*ba73b2c8SAndreas Gohr } 181*ba73b2c8SAndreas Gohr $R->listitem_close(); 182*ba73b2c8SAndreas Gohr } 183*ba73b2c8SAndreas Gohr $R->listu_close(); 1841169a1acSAndreas Gohr } 1851169a1acSAndreas Gohr 186d8ce5486SAndreas Gohr /** 187*ba73b2c8SAndreas Gohr * Check if the given parent ID is a parent of the child ID 188d8ce5486SAndreas Gohr * 189*ba73b2c8SAndreas Gohr * @param string $child 190*ba73b2c8SAndreas Gohr * @param string $parent 191d8ce5486SAndreas Gohr * @return bool 192d8ce5486SAndreas Gohr */ 193*ba73b2c8SAndreas Gohr protected function isParent(string $child, string $parent) 194d8ce5486SAndreas Gohr { 195*ba73b2c8SAndreas Gohr $child = explode(':', $child); 196*ba73b2c8SAndreas Gohr $parent = explode(':', $parent); 197*ba73b2c8SAndreas Gohr return array_slice($child, 0, count($parent)) === $parent; 1981169a1acSAndreas Gohr } 1991169a1acSAndreas Gohr 200d418c031SAndreas Gohr 201d418c031SAndreas Gohr /** 202d8ce5486SAndreas Gohr * Get the title for the given page ID 203d8ce5486SAndreas Gohr * 204d8ce5486SAndreas Gohr * @param string $id 205e75a33bfSAndreas Gohr * @param bool $usetitle - use the first heading as title 206d8ce5486SAndreas Gohr * @return string 207d8ce5486SAndreas Gohr */ 208e75a33bfSAndreas Gohr protected function getTitle($id, $usetitle) 209d8ce5486SAndreas Gohr { 210e306992cSAndreas Gohr global $conf; 211e306992cSAndreas Gohr 212e75a33bfSAndreas Gohr if ($usetitle) { 213e306992cSAndreas Gohr $p = p_get_first_heading($id); 214303e1405SMichael Große if (!empty($p)) return $p; 215e75a33bfSAndreas Gohr } 216e306992cSAndreas Gohr 217e306992cSAndreas Gohr $p = noNS($id); 218d8ce5486SAndreas Gohr if ($p == $conf['start'] || !$p) { 219e306992cSAndreas Gohr $p = noNS(getNS($id)); 220d8ce5486SAndreas Gohr if (!$p) { 221e306992cSAndreas Gohr return $conf['start']; 222e306992cSAndreas Gohr } 223e306992cSAndreas Gohr } 224e306992cSAndreas Gohr return $p; 225e306992cSAndreas Gohr } 2261169a1acSAndreas Gohr} 227