*/ class syntax_plugin_simplenavi extends SyntaxPlugin { protected string $ns; protected string $currentID; protected bool $usetitle; protected string $sort; protected bool $home; protected int $peek = 0; protected bool $filter = false; /** @inheritdoc */ public function getType() { return 'substition'; } /** @inheritdoc */ public function getPType() { return 'block'; } /** @inheritdoc */ public function getSort() { return 155; } /** @inheritdoc */ public function connectTo($mode) { $this->Lexer->addSpecialPattern('{{simplenavi>[^}]*}}', $mode, 'plugin_simplenavi'); } /** @inheritdoc */ public function handle($match, $state, $pos, Doku_Handler $handler) { return explode(' ', substr($match, 13, -2)); } /** @inheritdoc */ public function render($format, Doku_Renderer $renderer, $data) { if ($format != 'xhtml') return false; $renderer->nocache(); global $INFO; // first data is namespace, rest is options $ns = array_shift($data); if ($ns && $ns[0] === '.') { // resolve relative to current page $ns = getNS((new PageResolver($INFO['id']))->resolveId("$ns:xxx")); } else { $ns = cleanID($ns); } $this->initState( $ns, $INFO['id'], (bool)$this->getConf('usetitle'), $this->getConf('sort'), in_array('home', $data), $this->getConf('peek', 0), in_array('filter', $data) ); $tree = $this->getTree(); $class = 'plugin__simplenavi'; if ($this->filter) { $class .= ' plugin__simplenavi_filter'; } $renderer->doc .= '
'; $this->renderTree($renderer, $tree->getTop()); $renderer->doc .= '
'; return true; } /** * Initialize the configuration state of the plugin * * Also used in testing */ public function initState( string $ns, string $currentID, bool $usetitle, string $sort, bool $home, int $peek = 0, bool $filter = false ) { $this->ns = $ns; $this->currentID = $currentID; $this->usetitle = $usetitle; $this->sort = $sort; $this->home = $home; $this->peek = $peek; $this->filter = $filter; } /** * Create the tree * * @return PageTreeBuilder */ protected function getTree(): PageTreeBuilder { $tree = new PageTreeBuilder($this->ns); $tree->addFlag(PageTreeBuilder::FLAG_NS_AS_STARTPAGE); if ($this->home) $tree->addFlag(PageTreeBuilder::FLAG_SELF_TOP); $tree->setRecursionDecision(\Closure::fromCallable([$this, 'treeRecursionDecision'])); $tree->setNodeProcessor(\Closure::fromCallable([$this, 'treeNodeProcessor'])); $tree->generate(); switch ($this->sort) { case 'id': $tree->sort(TreeSort::SORT_BY_ID); break; case 'title': $tree->sort(TreeSort::SORT_BY_TITLE); break; case 'ns_id': $tree->sort(TreeSort::SORT_BY_NS_FIRST_THEN_ID); break; default: $tree->sort(TreeSort::SORT_BY_NS_FIRST_THEN_TITLE); break; } return $tree; } /** * Callback for the PageTreeBuilder to decide if we want to recurse into a node * * @param AbstractNode $node * @param int $depth * @return bool */ protected function treeRecursionDecision(AbstractNode $node, int $depth): bool { if ($node instanceof WikiStartpage) { $id = $node->getNs(); // use the namespace for startpages } else { $id = $node->getId(); } $is_current = $this->isParent($this->currentID, $id); $node->setProperty('is_current', $is_current); // always recurse into the current page path if ($is_current) return true; // should we peek deeper to see if there's something readable? if ($depth < $this->peek && auth_quickaclcheck($node->getId()) < AUTH_READ) { return true; } return false; } /** * Callback for the PageTreeBuilder to process a node * * @param AbstractNode $node * @return AbstractNode|null */ protected function treeNodeProcessor(AbstractNode $node): ?AbstractNode { $perm = auth_quickaclcheck($node->getId()); $node->setProperty('permission', $perm); $node->setTitle($this->getTitle($node->getId())); if ($node->hasChildren()) { // this node has children, we add it to the tree regardless of the permission // permissions are checked again when rendering return $node; } if ($perm < AUTH_READ) { // no children, no permission. No need to add it to the tree return null; } // don't show hidden pages if(isHiddenPage($node->getId())) return null; return $node; } /** * Render the tree * * @param Doku_Renderer $R The current renderer * @param AbstractNode $top The top node of the tree (use getTop() to get it) * @param int $level current nesting level, starting at 1 * @return void */ protected function renderTree(Doku_Renderer $R, AbstractNode $top, $level = 1) { $R->listu_open(); foreach ($top->getChildren() as $node) { $isfolder = $node instanceof WikiNamespace; $incurrent = $node->getProperty('is_current', false); $R->listitem_open(1, $isfolder); $R->listcontent_open(); if ($incurrent) $R->strong_open(); if (((int)$node->getProperty('permission', 0)) < AUTH_READ) { $R->cdata($node->getTitle()); } else { $R->internallink($node->getId(), $node->getTitle(), null, false, 'navigation'); } if ($incurrent) $R->strong_close(); $R->listcontent_close(); if ($node->hasChildren()) { $this->renderTree($R, $node, $level + 1); } $R->listitem_close(); } $R->listu_close(); } /** * Check if the given parent ID is a parent of the child ID * * @param string $child * @param string $parent * @return bool */ protected function isParent(string $child, string $parent) { // Empty parent is considered a parent of all pages if ($parent === '') { return true; } $child = explode(':', $child); $parent = explode(':', $parent); return array_slice($child, 0, count($parent)) === $parent; } /** * Get the title for the given page ID * * @param string $id * @return string */ protected function getTitle($id) { global $conf; if ($this->usetitle) { $p = p_get_first_heading($id); if (!empty($p)) return $p; } $p = noNS($id); if ($p == $conf['start'] || !$p) { $p = noNS(getNS($id)); if (!$p) { return $conf['start']; } } return $p; } }