11169a1acSAndreas Gohr<?php 2d8ce5486SAndreas Gohr 3d418c031SAndreas Gohruse dokuwiki\Extension\SyntaxPlugin; 4d8ce5486SAndreas Gohruse dokuwiki\File\PageResolver; 5ba73b2c8SAndreas Gohruse dokuwiki\TreeBuilder\Node\AbstractNode; 6ba73b2c8SAndreas Gohruse dokuwiki\TreeBuilder\Node\WikiNamespace; 7ba73b2c8SAndreas Gohruse dokuwiki\TreeBuilder\Node\WikiStartpage; 8ba73b2c8SAndreas Gohruse dokuwiki\TreeBuilder\PageTreeBuilder; 9ba73b2c8SAndreas 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{ 194c5e7fe5SAndreas Gohr protected string $ns; 204c5e7fe5SAndreas Gohr protected string $currentID; 214c5e7fe5SAndreas Gohr protected bool $usetitle; 224c5e7fe5SAndreas Gohr protected string $sort; 234c5e7fe5SAndreas Gohr protected bool $home; 24*8a3822efSAndreas Gohr protected int $peek=0; 251169a1acSAndreas Gohr 26d8ce5486SAndreas Gohr /** @inheritdoc */ 27d8ce5486SAndreas Gohr public function getType() 28d8ce5486SAndreas Gohr { 291169a1acSAndreas Gohr return 'substition'; 301169a1acSAndreas Gohr } 311169a1acSAndreas Gohr 32d8ce5486SAndreas Gohr /** @inheritdoc */ 33d8ce5486SAndreas Gohr public function getPType() 34d8ce5486SAndreas Gohr { 351169a1acSAndreas Gohr return 'block'; 361169a1acSAndreas Gohr } 371169a1acSAndreas Gohr 38d8ce5486SAndreas Gohr /** @inheritdoc */ 39d8ce5486SAndreas Gohr public function getSort() 40d8ce5486SAndreas Gohr { 411169a1acSAndreas Gohr return 155; 421169a1acSAndreas Gohr } 431169a1acSAndreas Gohr 44d8ce5486SAndreas Gohr /** @inheritdoc */ 45d8ce5486SAndreas Gohr public function connectTo($mode) 46d8ce5486SAndreas Gohr { 471169a1acSAndreas Gohr $this->Lexer->addSpecialPattern('{{simplenavi>[^}]*}}', $mode, 'plugin_simplenavi'); 481169a1acSAndreas Gohr } 491169a1acSAndreas Gohr 50d8ce5486SAndreas Gohr /** @inheritdoc */ 51d8ce5486SAndreas Gohr public function handle($match, $state, $pos, Doku_Handler $handler) 52d8ce5486SAndreas Gohr { 535655937aSAndreas Gohr return explode(' ', substr($match, 13, -2)); 541169a1acSAndreas Gohr } 551169a1acSAndreas Gohr 56d8ce5486SAndreas Gohr /** @inheritdoc */ 57d8ce5486SAndreas Gohr public function render($format, Doku_Renderer $renderer, $data) 58d8ce5486SAndreas Gohr { 59d8ce5486SAndreas Gohr if ($format != 'xhtml') return false; 604c5e7fe5SAndreas Gohr $renderer->nocache(); 611169a1acSAndreas Gohr 621169a1acSAndreas Gohr global $INFO; 631169a1acSAndreas Gohr 64b3e02951SAndreas Gohr // first data is namespace, rest is options 655655937aSAndreas Gohr $ns = array_shift($data); 665655937aSAndreas Gohr if ($ns && $ns[0] === '.') { 675655937aSAndreas Gohr // resolve relative to current page 685655937aSAndreas Gohr $ns = getNS((new PageResolver($INFO['id']))->resolveId("$ns:xxx")); 695655937aSAndreas Gohr } else { 705655937aSAndreas Gohr $ns = cleanID($ns); 715655937aSAndreas Gohr } 72b3e02951SAndreas Gohr 734c5e7fe5SAndreas Gohr $this->initState( 744c5e7fe5SAndreas Gohr $ns, 754c5e7fe5SAndreas Gohr $INFO['id'], 764c5e7fe5SAndreas Gohr (bool)$this->getConf('usetitle'), 774c5e7fe5SAndreas Gohr $this->getConf('sort'), 78*8a3822efSAndreas Gohr in_array('home', $data), 79*8a3822efSAndreas Gohr $this->getConf('peek', 0), 804c5e7fe5SAndreas Gohr ); 814c5e7fe5SAndreas Gohr 82*8a3822efSAndreas Gohr $tree = $this->getTree(); 83ba73b2c8SAndreas Gohr $this->renderTree($renderer, $tree->getTop()); 841169a1acSAndreas Gohr 851169a1acSAndreas Gohr return true; 861169a1acSAndreas Gohr } 871169a1acSAndreas Gohr 88d8ce5486SAndreas Gohr /** 894c5e7fe5SAndreas Gohr * Initialize the configuration state of the plugin 904c5e7fe5SAndreas Gohr * 914c5e7fe5SAndreas Gohr * Also used in testing 92e75a33bfSAndreas Gohr * 93ba73b2c8SAndreas Gohr * @param string $ns 944c5e7fe5SAndreas Gohr * @param string $currentID 954c5e7fe5SAndreas Gohr * @param bool $usetitle 964c5e7fe5SAndreas Gohr */ 974c5e7fe5SAndreas Gohr public function initState( 984c5e7fe5SAndreas Gohr string $ns, 994c5e7fe5SAndreas Gohr string $currentID, 1004c5e7fe5SAndreas Gohr bool $usetitle, 1014c5e7fe5SAndreas Gohr string $sort, 102*8a3822efSAndreas Gohr bool $home, 103*8a3822efSAndreas Gohr int $peek = 0 1044c5e7fe5SAndreas Gohr ) 1054c5e7fe5SAndreas Gohr { 1064c5e7fe5SAndreas Gohr $this->ns = $ns; 1074c5e7fe5SAndreas Gohr $this->currentID = $currentID; 1084c5e7fe5SAndreas Gohr $this->usetitle = $usetitle; 1094c5e7fe5SAndreas Gohr $this->sort = $sort; 1104c5e7fe5SAndreas Gohr $this->home = $home; 111*8a3822efSAndreas Gohr $this->peek = $peek; 1124c5e7fe5SAndreas Gohr } 1134c5e7fe5SAndreas Gohr 1144c5e7fe5SAndreas Gohr /** 1154c5e7fe5SAndreas Gohr * Create the tree 1164c5e7fe5SAndreas Gohr * 117ba73b2c8SAndreas Gohr * @return PageTreeBuilder 118e75a33bfSAndreas Gohr */ 1194c5e7fe5SAndreas Gohr protected function getTree(): PageTreeBuilder 120e75a33bfSAndreas Gohr { 1214c5e7fe5SAndreas Gohr $tree = new PageTreeBuilder($this->ns); 122ba73b2c8SAndreas Gohr $tree->addFlag(PageTreeBuilder::FLAG_NS_AS_STARTPAGE); 1234c5e7fe5SAndreas Gohr if ($this->home) $tree->addFlag(PageTreeBuilder::FLAG_SELF_TOP); 124ba73b2c8SAndreas Gohr $tree->setRecursionDecision(\Closure::fromCallable([$this, 'treeRecursionDecision'])); 125ba73b2c8SAndreas Gohr $tree->setNodeProcessor(\Closure::fromCallable([$this, 'treeNodeProcessor'])); 126ba73b2c8SAndreas Gohr $tree->generate(); 1274c5e7fe5SAndreas Gohr 1284c5e7fe5SAndreas Gohr switch ($this->sort) { 1294c5e7fe5SAndreas Gohr case 'id': 1304c5e7fe5SAndreas Gohr $tree->sort(TreeSort::SORT_BY_ID); 1314c5e7fe5SAndreas Gohr break; 1324c5e7fe5SAndreas Gohr case 'title': 1334c5e7fe5SAndreas Gohr $tree->sort(TreeSort::SORT_BY_TITLE); 1344c5e7fe5SAndreas Gohr break; 1354c5e7fe5SAndreas Gohr case 'ns_id': 1364c5e7fe5SAndreas Gohr $tree->sort(TreeSort::SORT_BY_NS_FIRST_THEN_ID); 1374c5e7fe5SAndreas Gohr break; 1384c5e7fe5SAndreas Gohr default: 139ba73b2c8SAndreas Gohr $tree->sort(TreeSort::SORT_BY_NS_FIRST_THEN_TITLE); 1404c5e7fe5SAndreas Gohr break; 1414c5e7fe5SAndreas Gohr } 1424c5e7fe5SAndreas Gohr 143ba73b2c8SAndreas Gohr return $tree; 144e75a33bfSAndreas Gohr } 145e75a33bfSAndreas Gohr 146e75a33bfSAndreas Gohr 147e75a33bfSAndreas Gohr /** 148ba73b2c8SAndreas Gohr * Callback for the PageTreeBuilder to decide if we want to recurse into a node 149d8ce5486SAndreas Gohr * 150ba73b2c8SAndreas Gohr * @param AbstractNode $node 151ba73b2c8SAndreas Gohr * @param int $depth 152ba73b2c8SAndreas Gohr * @return bool 153d8ce5486SAndreas Gohr */ 154ba73b2c8SAndreas Gohr protected function treeRecursionDecision(AbstractNode $node, int $depth): bool 155d8ce5486SAndreas Gohr { 156ba73b2c8SAndreas Gohr if ($node instanceof WikiStartpage) { 157ba73b2c8SAndreas Gohr $id = $node->getNs(); // use the namespace for startpages 158492ddc4eSAndreas Gohr } else { 159ba73b2c8SAndreas Gohr $id = $node->getId(); 160492ddc4eSAndreas Gohr } 161ba73b2c8SAndreas Gohr 1624c5e7fe5SAndreas Gohr $is_current = $this->isParent($this->currentID, $id); 163ba73b2c8SAndreas Gohr $node->setProperty('is_current', $is_current); 164ba73b2c8SAndreas Gohr 165ba73b2c8SAndreas Gohr // always recurse into the current page path 166ba73b2c8SAndreas Gohr if ($is_current) return true; 167ba73b2c8SAndreas Gohr 168*8a3822efSAndreas Gohr // should we peek deeper to see if there's something readable? 169*8a3822efSAndreas Gohr if($depth < $this->peek && auth_quickaclcheck($node->getId()) < AUTH_READ ) { 170*8a3822efSAndreas Gohr return true; 171*8a3822efSAndreas Gohr } 172ba73b2c8SAndreas Gohr 173ba73b2c8SAndreas Gohr return false; 1741169a1acSAndreas Gohr } 1751169a1acSAndreas Gohr 176d8ce5486SAndreas Gohr /** 177ba73b2c8SAndreas Gohr * Callback for the PageTreeBuilder to process a node 178d8ce5486SAndreas Gohr * 179ba73b2c8SAndreas Gohr * @param AbstractNode $node 180ba73b2c8SAndreas Gohr * @return AbstractNode|null 181d8ce5486SAndreas Gohr */ 182ba73b2c8SAndreas Gohr protected function treeNodeProcessor(AbstractNode $node): ?AbstractNode 183d8ce5486SAndreas Gohr { 184ba73b2c8SAndreas Gohr $perm = auth_quickaclcheck($node->getId()); 185ba73b2c8SAndreas Gohr $node->setProperty('permission', $perm); 1864c5e7fe5SAndreas Gohr $node->setTitle($this->getTitle($node->getId())); 187ba73b2c8SAndreas Gohr 188ba73b2c8SAndreas Gohr 189ba73b2c8SAndreas Gohr if ($node->hasChildren()) { 190ba73b2c8SAndreas Gohr // this node has children, we add it to the tree regardless of the permission 191ba73b2c8SAndreas Gohr // permissions are checked again when rendering 192ba73b2c8SAndreas Gohr return $node; 1931169a1acSAndreas Gohr } 194ba73b2c8SAndreas Gohr 195ba73b2c8SAndreas Gohr if ($perm < AUTH_READ) { 196ba73b2c8SAndreas Gohr // no children, no permission. No need to add it to the tree 197ba73b2c8SAndreas Gohr return null; 198ba73b2c8SAndreas Gohr } 199ba73b2c8SAndreas Gohr 200ba73b2c8SAndreas Gohr return $node; 201ba73b2c8SAndreas Gohr } 202ba73b2c8SAndreas Gohr 203ba73b2c8SAndreas Gohr 204ba73b2c8SAndreas Gohr /** 205ba73b2c8SAndreas Gohr * Example on how to render a TreeBuilder tree 206ba73b2c8SAndreas Gohr * 207ba73b2c8SAndreas Gohr * @param Doku_Renderer $R The current renderer 208ba73b2c8SAndreas Gohr * @param AbstractNode $top The top node of the tree (use getTop() to get it) 209ba73b2c8SAndreas Gohr * @param int $level current nesting level, starting at 1 210ba73b2c8SAndreas Gohr * @return void 211ba73b2c8SAndreas Gohr */ 212ba73b2c8SAndreas Gohr protected function renderTree(Doku_Renderer $R, AbstractNode $top, $level = 1) 213ba73b2c8SAndreas Gohr { 214ba73b2c8SAndreas Gohr $R->listu_open(); 215ba73b2c8SAndreas Gohr foreach ($top->getChildren() as $node) { 216ba73b2c8SAndreas Gohr $isfolder = $node instanceof WikiNamespace; 217ba73b2c8SAndreas Gohr $incurrent = $node->getProperty('is_current', false); 218ba73b2c8SAndreas Gohr 219ba73b2c8SAndreas Gohr $R->listitem_open(1, $isfolder); 220ba73b2c8SAndreas Gohr $R->listcontent_open(); 221ba73b2c8SAndreas Gohr if ($incurrent) $R->strong_open(); 222ba73b2c8SAndreas Gohr 223ba73b2c8SAndreas Gohr if (((int)$node->getProperty('permission', 0)) < AUTH_READ) { 224ba73b2c8SAndreas Gohr $R->cdata($node->getTitle()); 225ba73b2c8SAndreas Gohr } else { 226ba73b2c8SAndreas Gohr $R->internallink($node->getId(), $node->getTitle(), null, false, 'navigation'); 227ba73b2c8SAndreas Gohr } 228ba73b2c8SAndreas Gohr 229ba73b2c8SAndreas Gohr if ($incurrent) $R->strong_close(); 230ba73b2c8SAndreas Gohr $R->listcontent_close(); 231ba73b2c8SAndreas Gohr if ($node->hasChildren()) { 232ba73b2c8SAndreas Gohr $this->renderTree($R, $node, $level + 1); 233ba73b2c8SAndreas Gohr } 234ba73b2c8SAndreas Gohr $R->listitem_close(); 235ba73b2c8SAndreas Gohr } 236ba73b2c8SAndreas Gohr $R->listu_close(); 2371169a1acSAndreas Gohr } 2381169a1acSAndreas Gohr 239d8ce5486SAndreas Gohr /** 240ba73b2c8SAndreas Gohr * Check if the given parent ID is a parent of the child ID 241d8ce5486SAndreas Gohr * 242ba73b2c8SAndreas Gohr * @param string $child 243ba73b2c8SAndreas Gohr * @param string $parent 244d8ce5486SAndreas Gohr * @return bool 245d8ce5486SAndreas Gohr */ 246ba73b2c8SAndreas Gohr protected function isParent(string $child, string $parent) 247d8ce5486SAndreas Gohr { 248ba73b2c8SAndreas Gohr $child = explode(':', $child); 249ba73b2c8SAndreas Gohr $parent = explode(':', $parent); 250ba73b2c8SAndreas Gohr return array_slice($child, 0, count($parent)) === $parent; 2511169a1acSAndreas Gohr } 2521169a1acSAndreas Gohr 253d418c031SAndreas Gohr 254d418c031SAndreas Gohr /** 255d8ce5486SAndreas Gohr * Get the title for the given page ID 256d8ce5486SAndreas Gohr * 257d8ce5486SAndreas Gohr * @param string $id 258d8ce5486SAndreas Gohr * @return string 259d8ce5486SAndreas Gohr */ 2604c5e7fe5SAndreas Gohr protected function getTitle($id) 261d8ce5486SAndreas Gohr { 262e306992cSAndreas Gohr global $conf; 263e306992cSAndreas Gohr 2644c5e7fe5SAndreas Gohr if ($this->usetitle) { 265e306992cSAndreas Gohr $p = p_get_first_heading($id); 266303e1405SMichael Große if (!empty($p)) return $p; 267e75a33bfSAndreas Gohr } 268e306992cSAndreas Gohr 269e306992cSAndreas Gohr $p = noNS($id); 270d8ce5486SAndreas Gohr if ($p == $conf['start'] || !$p) { 271e306992cSAndreas Gohr $p = noNS(getNS($id)); 272d8ce5486SAndreas Gohr if (!$p) { 273e306992cSAndreas Gohr return $conf['start']; 274e306992cSAndreas Gohr } 275e306992cSAndreas Gohr } 276e306992cSAndreas Gohr return $p; 277e306992cSAndreas Gohr } 2781169a1acSAndreas Gohr} 279