1*78a26510SAndreas Gohr<?php 2*78a26510SAndreas Gohr 3*78a26510SAndreas Gohrnamespace dokuwiki\TreeBuilder; 4*78a26510SAndreas Gohr 5*78a26510SAndreas Gohruse dokuwiki\File\PageResolver; 6*78a26510SAndreas Gohruse dokuwiki\TreeBuilder\Node\AbstractNode; 7*78a26510SAndreas Gohruse dokuwiki\TreeBuilder\Node\ExternalLink; 8*78a26510SAndreas Gohruse dokuwiki\TreeBuilder\Node\Top; 9*78a26510SAndreas Gohruse dokuwiki\TreeBuilder\Node\WikiPage; 10*78a26510SAndreas Gohr 11*78a26510SAndreas Gohr/** 12*78a26510SAndreas Gohr * A tree builder that generates a tree from a control page 13*78a26510SAndreas Gohr * 14*78a26510SAndreas Gohr * A control page is a wiki page containing a nested list of external and internal links. This builder 15*78a26510SAndreas Gohr * parses the control page and generates a tree of nodes representing the links. 16*78a26510SAndreas Gohr */ 17*78a26510SAndreas Gohrclass ControlPageBuilder extends AbstractBuilder 18*78a26510SAndreas Gohr{ 19*78a26510SAndreas Gohr /** @var int do not include internal links */ 20*78a26510SAndreas Gohr const FLAG_NOINTERNAL = 1; 21*78a26510SAndreas Gohr /** @var int do not include external links */ 22*78a26510SAndreas Gohr const FLAG_NOEXTERNAL = 2; 23*78a26510SAndreas Gohr 24*78a26510SAndreas Gohr /** @var string */ 25*78a26510SAndreas Gohr protected string $controlPage; 26*78a26510SAndreas Gohr /** @var int */ 27*78a26510SAndreas Gohr protected int $flags = 0; 28*78a26510SAndreas Gohr 29*78a26510SAndreas Gohr /** 30*78a26510SAndreas Gohr * Parse the control page 31*78a26510SAndreas Gohr * 32*78a26510SAndreas Gohr * Check the flag constants on how to influence the behaviour 33*78a26510SAndreas Gohr * 34*78a26510SAndreas Gohr * @param string $controlPage 35*78a26510SAndreas Gohr * @param int $flags 36*78a26510SAndreas Gohr */ 37*78a26510SAndreas Gohr public function __construct(string $controlPage) 38*78a26510SAndreas Gohr { 39*78a26510SAndreas Gohr $this->controlPage = $controlPage; 40*78a26510SAndreas Gohr } 41*78a26510SAndreas Gohr 42*78a26510SAndreas Gohr /** 43*78a26510SAndreas Gohr * @inheritdoc 44*78a26510SAndreas Gohr * @todo theoretically it should be possible to also take the recursionDecision into account, even though 45*78a26510SAndreas Gohr * we don't recurse here. Passing the depth would be easy, but actually aborting going deeper is difficult. 46*78a26510SAndreas Gohr */ 47*78a26510SAndreas Gohr public function generate(): void 48*78a26510SAndreas Gohr { 49*78a26510SAndreas Gohr $this->top = new Top(); 50*78a26510SAndreas Gohr $instructions = p_cached_instructions(wikiFN($this->controlPage)); 51*78a26510SAndreas Gohr if (!$instructions) { 52*78a26510SAndreas Gohr throw new \RuntimeException('No instructions for control page found'); 53*78a26510SAndreas Gohr } 54*78a26510SAndreas Gohr 55*78a26510SAndreas Gohr $parents = [ 56*78a26510SAndreas Gohr 0 => $this->top 57*78a26510SAndreas Gohr ]; 58*78a26510SAndreas Gohr $level = 0; 59*78a26510SAndreas Gohr 60*78a26510SAndreas Gohr $resolver = new PageResolver($this->controlPage); 61*78a26510SAndreas Gohr 62*78a26510SAndreas Gohr foreach ($instructions as $instruction) { 63*78a26510SAndreas Gohr switch ($instruction[0]) { 64*78a26510SAndreas Gohr case 'listu_open': 65*78a26510SAndreas Gohr $level++; // new list level 66*78a26510SAndreas Gohr break; 67*78a26510SAndreas Gohr case 'listu_close': 68*78a26510SAndreas Gohr // if we had a node on this level, remove it from the parents 69*78a26510SAndreas Gohr if(isset($parents[$level])) { 70*78a26510SAndreas Gohr unset($parents[$level]); 71*78a26510SAndreas Gohr } 72*78a26510SAndreas Gohr $level--; // close list level 73*78a26510SAndreas Gohr break; 74*78a26510SAndreas Gohr case 'internallink': 75*78a26510SAndreas Gohr case 'externallink': 76*78a26510SAndreas Gohr if ($instruction[0] == 'internallink') { 77*78a26510SAndreas Gohr if ($this->flags & self::FLAG_NOINTERNAL) break; 78*78a26510SAndreas Gohr 79*78a26510SAndreas Gohr $newpage = new WikiPage( 80*78a26510SAndreas Gohr $resolver->resolveId($instruction[1][0]), 81*78a26510SAndreas Gohr $instruction[1][1] 82*78a26510SAndreas Gohr ); 83*78a26510SAndreas Gohr } else { 84*78a26510SAndreas Gohr if ($this->flags & self::FLAG_NOEXTERNAL) break; 85*78a26510SAndreas Gohr 86*78a26510SAndreas Gohr $newpage = new ExternalLink( 87*78a26510SAndreas Gohr $instruction[1][0], 88*78a26510SAndreas Gohr $instruction[1][1] 89*78a26510SAndreas Gohr ); 90*78a26510SAndreas Gohr } 91*78a26510SAndreas Gohr 92*78a26510SAndreas Gohr if($level) { 93*78a26510SAndreas Gohr // remember this page as the parent for this level 94*78a26510SAndreas Gohr $parents[$level] = $newpage; 95*78a26510SAndreas Gohr // parent is the last page on the previous level 96*78a26510SAndreas Gohr // levels may not be evenly distributed, so we need to check the count 97*78a26510SAndreas Gohr $parent = $parents[count($parents) - 2]; 98*78a26510SAndreas Gohr } else { 99*78a26510SAndreas Gohr // not in a list, so parent is always the top 100*78a26510SAndreas Gohr $parent = $this->top; 101*78a26510SAndreas Gohr } 102*78a26510SAndreas Gohr 103*78a26510SAndreas Gohr $newpage->setParent($parent); 104*78a26510SAndreas Gohr $newpage = $this->applyNodeProcessor($newpage); 105*78a26510SAndreas Gohr if($newpage instanceof AbstractNode) { 106*78a26510SAndreas Gohr $parent->addChild($newpage); 107*78a26510SAndreas Gohr } 108*78a26510SAndreas Gohr break; 109*78a26510SAndreas Gohr } 110*78a26510SAndreas Gohr } 111*78a26510SAndreas Gohr 112*78a26510SAndreas Gohr $this->generated = true; 113*78a26510SAndreas Gohr } 114*78a26510SAndreas Gohr} 115