nodeProcessor = $builder; } /** * Set a callback to decide if recursion should happen * * The callback receives a Node as parameter and the current recursion depth. * The node will NOT have it's children set. * The callback must return true to have any children added, false to skip them. * * @param callable|null $filter * @return void */ public function setRecursionDecision(?callable $filter): void { if ($filter !== null && !is_callable($filter)) { throw new \InvalidArgumentException('Recursion-filter must be callable'); } $this->recursionDecision = $filter; } /** * Add a configuration flag * * @param int $flag * @return void */ public function addFlag(int $flag): void { $this->flags |= $flag; } /** * Check if a flag is set * * @param int $flag * @return bool */ public function hasFlag(int $flag): bool { return ($this->flags & $flag) === $flag; } /** * Check if a flag is NOT set * * @param int $flag * @return bool */ public function hasNotFlag(int $flag): bool { return ($this->flags & $flag) !== $flag; } /** * Remove a configuration flag * * @param int $flag * @return void */ public function removeFlag(int $flag): void { $this->flags &= ~$flag; } /** * Access the top element * * Use it's children to iterate over the page hierarchy * * @return Top */ public function getTop(): Top { if (!$this->generated) throw new \RuntimeException('need to call generate() first'); return $this->top; } /** * Get a flat list of all nodes in the tree * * This is a cached version of top->getDescendants() with the ID as key of the returned array. * * @return AbstractNode[] */ public function getAll(): array { if (!$this->generated) throw new \RuntimeException('need to call generate() first'); if ($this->nodes === []) { $this->nodes = []; foreach ($this->top->getDescendants() as $node) { $this->nodes[$node->getId()] = $node; } } return $this->nodes; } /** * Get a flat list of all nodes that do NOT have children * * @return AbstractNode[] */ public function getLeaves(): array { if (!$this->generated) throw new \RuntimeException('need to call generate() first'); return array_filter($this->getAll(), fn($page) => !$page->getChildren()); } /** * Get a flat list of all nodes that DO have children * * @return AbstractNode[] */ public function getBranches(): array { if (!$this->generated) throw new \RuntimeException('need to call generate() first'); return array_filter($this->getAll(), fn($page) => (bool) $page->getChildren()); } /** * Sort the tree * * The given comparator function will be called with two nodes as arguments and needs to * return an integer less than, equal to, or greater than zero if the first argument is considered * to be respectively less than, equal to, or greater than the second. * * Pass in one of the TreeSort comparators or your own. * * @param callable $comparator * @return void */ public function sort(callable $comparator): void { if (!$this->generated) throw new \RuntimeException('need to call generate() first'); $this->top->sort($comparator); $this->nodes = []; // reset the cache } /** * Render the tree on the given renderer * * This is mostly an example implementation. You probably want to implement your own. * * @param Doku_Renderer $R The current renderer * @param AbstractNode $top The node to start from, use null to start from the top node * @param int $level current nesting level, starting at 1 * @return void */ public function render(Doku_Renderer $R, $top = null, $level = 1): void { if ($top === null) $top = $this->getTop(); $R->listu_open(); foreach ($top->getChildren() as $node) { $R->listitem_open(1, $node->hasChildren()); $R->listcontent_open(); if ($node instanceof ExternalLink) { $R->externallink($node->getId(), $node->getTitle()); } else { $R->internallink($node->getId(), $node->getTitle()); } $R->listcontent_close(); if ($node->hasChildren()) { $this->render($R, $node, $level + 1); } $R->listitem_close(); } $R->listu_close(); } /** * @param AbstractNode $node * @return AbstractNode|null */ protected function applyNodeProcessor(AbstractNode $node): ?AbstractNode { if ($this->nodeProcessor === null) return $node; $result = call_user_func($this->nodeProcessor, $node); if (!$result instanceof AbstractNode) return null; return $result; } /** * @param AbstractNode $node * @return bool should children be added? */ protected function applyRecursionDecision(AbstractNode $node, int $depth): bool { if ($this->recursionDecision === null) return true; return (bool)call_user_func($this->recursionDecision, $node, $depth); } /** * "prints" the tree * * @return array */ public function __toString(): string { return implode("\n", $this->getAll()); } }