178a26510SAndreas Gohr<?php 278a26510SAndreas Gohr 378a26510SAndreas Gohrnamespace dokuwiki\TreeBuilder; 478a26510SAndreas Gohr 578a26510SAndreas Gohruse dokuwiki\File\PageResolver; 678a26510SAndreas Gohruse dokuwiki\TreeBuilder\Node\AbstractNode; 778a26510SAndreas Gohruse dokuwiki\TreeBuilder\Node\ExternalLink; 878a26510SAndreas Gohruse dokuwiki\TreeBuilder\Node\Top; 978a26510SAndreas Gohruse dokuwiki\TreeBuilder\Node\WikiPage; 1078a26510SAndreas Gohr 1178a26510SAndreas Gohr/** 1278a26510SAndreas Gohr * A tree builder that generates a tree from a control page 1378a26510SAndreas Gohr * 1478a26510SAndreas Gohr * A control page is a wiki page containing a nested list of external and internal links. This builder 1578a26510SAndreas Gohr * parses the control page and generates a tree of nodes representing the links. 1678a26510SAndreas Gohr */ 1778a26510SAndreas Gohrclass ControlPageBuilder extends AbstractBuilder 1878a26510SAndreas Gohr{ 1978a26510SAndreas Gohr /** @var int do not include internal links */ 20*a8e9ec06SAndreas Gohr public const FLAG_NOINTERNAL = 1; 2178a26510SAndreas Gohr /** @var int do not include external links */ 22*a8e9ec06SAndreas Gohr public const FLAG_NOEXTERNAL = 2; 2378a26510SAndreas Gohr 2478a26510SAndreas Gohr /** @var string */ 2578a26510SAndreas Gohr protected string $controlPage; 2678a26510SAndreas Gohr /** @var int */ 2778a26510SAndreas Gohr protected int $flags = 0; 2878a26510SAndreas Gohr 2978a26510SAndreas Gohr /** 3078a26510SAndreas Gohr * Parse the control page 3178a26510SAndreas Gohr * 3278a26510SAndreas Gohr * Check the flag constants on how to influence the behaviour 3378a26510SAndreas Gohr * 3478a26510SAndreas Gohr * @param string $controlPage 3578a26510SAndreas Gohr * @param int $flags 3678a26510SAndreas Gohr */ 3778a26510SAndreas Gohr public function __construct(string $controlPage) 3878a26510SAndreas Gohr { 3978a26510SAndreas Gohr $this->controlPage = $controlPage; 4078a26510SAndreas Gohr } 4178a26510SAndreas Gohr 4278a26510SAndreas Gohr /** 4378a26510SAndreas Gohr * @inheritdoc 4478a26510SAndreas Gohr * @todo theoretically it should be possible to also take the recursionDecision into account, even though 4578a26510SAndreas Gohr * we don't recurse here. Passing the depth would be easy, but actually aborting going deeper is difficult. 4678a26510SAndreas Gohr */ 4778a26510SAndreas Gohr public function generate(): void 4878a26510SAndreas Gohr { 4978a26510SAndreas Gohr $this->top = new Top(); 5078a26510SAndreas Gohr $instructions = p_cached_instructions(wikiFN($this->controlPage)); 5178a26510SAndreas Gohr if (!$instructions) { 5278a26510SAndreas Gohr throw new \RuntimeException('No instructions for control page found'); 5378a26510SAndreas Gohr } 5478a26510SAndreas Gohr 5578a26510SAndreas Gohr $parents = [ 5678a26510SAndreas Gohr 0 => $this->top 5778a26510SAndreas Gohr ]; 5878a26510SAndreas Gohr $level = 0; 5978a26510SAndreas Gohr 6078a26510SAndreas Gohr $resolver = new PageResolver($this->controlPage); 6178a26510SAndreas Gohr 6278a26510SAndreas Gohr foreach ($instructions as $instruction) { 6378a26510SAndreas Gohr switch ($instruction[0]) { 6478a26510SAndreas Gohr case 'listu_open': 6578a26510SAndreas Gohr $level++; // new list level 6678a26510SAndreas Gohr break; 6778a26510SAndreas Gohr case 'listu_close': 6878a26510SAndreas Gohr // if we had a node on this level, remove it from the parents 6978a26510SAndreas Gohr if (isset($parents[$level])) { 7078a26510SAndreas Gohr unset($parents[$level]); 7178a26510SAndreas Gohr } 7278a26510SAndreas Gohr $level--; // close list level 7378a26510SAndreas Gohr break; 7478a26510SAndreas Gohr case 'internallink': 7578a26510SAndreas Gohr case 'externallink': 7678a26510SAndreas Gohr if ($instruction[0] == 'internallink') { 7778a26510SAndreas Gohr if ($this->flags & self::FLAG_NOINTERNAL) break; 7878a26510SAndreas Gohr 7978a26510SAndreas Gohr $newpage = new WikiPage( 8078a26510SAndreas Gohr $resolver->resolveId($instruction[1][0]), 8178a26510SAndreas Gohr $instruction[1][1] 8278a26510SAndreas Gohr ); 8378a26510SAndreas Gohr } else { 8478a26510SAndreas Gohr if ($this->flags & self::FLAG_NOEXTERNAL) break; 8578a26510SAndreas Gohr 8678a26510SAndreas Gohr $newpage = new ExternalLink( 8778a26510SAndreas Gohr $instruction[1][0], 8878a26510SAndreas Gohr $instruction[1][1] 8978a26510SAndreas Gohr ); 9078a26510SAndreas Gohr } 9178a26510SAndreas Gohr 9278a26510SAndreas Gohr if ($level) { 9378a26510SAndreas Gohr // remember this page as the parent for this level 9478a26510SAndreas Gohr $parents[$level] = $newpage; 9578a26510SAndreas Gohr // parent is the last page on the previous level 9678a26510SAndreas Gohr // levels may not be evenly distributed, so we need to check the count 9778a26510SAndreas Gohr $parent = $parents[count($parents) - 2]; 9878a26510SAndreas Gohr } else { 9978a26510SAndreas Gohr // not in a list, so parent is always the top 10078a26510SAndreas Gohr $parent = $this->top; 10178a26510SAndreas Gohr } 10278a26510SAndreas Gohr 10378a26510SAndreas Gohr $newpage->setParent($parent); 10478a26510SAndreas Gohr $newpage = $this->applyNodeProcessor($newpage); 10578a26510SAndreas Gohr if ($newpage instanceof AbstractNode) { 10678a26510SAndreas Gohr $parent->addChild($newpage); 10778a26510SAndreas Gohr } 10878a26510SAndreas Gohr break; 10978a26510SAndreas Gohr } 11078a26510SAndreas Gohr } 11178a26510SAndreas Gohr 11278a26510SAndreas Gohr $this->generated = true; 11378a26510SAndreas Gohr } 11478a26510SAndreas Gohr} 115