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