1<?php
2
3namespace dokuwiki\TreeBuilder\Node;
4
5/**
6 * A node represents one entry in the tree. It can have a parent and children.
7 */
8abstract class AbstractNode
9{
10    /** @var AbstractNode|null parent node */
11    protected ?AbstractNode $parent = null;
12    /** @var string unique ID for this node, usually the page id or external URL */
13    protected string $id = '';
14    /** @var string|null title of the node, may be null */
15    protected ?string $title = null;
16
17    /** @var AbstractNode[] */
18    protected array $parents = [];
19    /** @var AbstractNode[] */
20    protected array $children = [];
21    /** @var array */
22    protected array $properties = [];
23
24    /**
25     * @param string $id The pageID or the external URL
26     * @param string|null $title The title as given in the link
27     */
28    public function __construct(string $id, ?string $title)
29    {
30        $this->id = $id;
31        $this->title = $title;
32    }
33
34    /**
35     * @return string
36     */
37    public function getId(): string
38    {
39        return $this->id;
40    }
41
42    /**
43     * Get the namespace of this node
44     *
45     * @return string
46     */
47    public function getNs(): string
48    {
49        return getNS($this->id);
50    }
51
52    /**
53     * @return string|null
54     */
55    public function getTitle(): ?string
56    {
57        return $this->title;
58    }
59
60    /**
61     * @param string|null $title
62     */
63    public function setTitle(?string $title): void
64    {
65        $this->title = $title;
66    }
67
68    /**
69     * Get all nodes on the same level
70     * @return AbstractNode[]
71     */
72    public function getSiblings(): array
73    {
74        return $this->getParent()->getChildren();
75    }
76
77    /**
78     * Get all sub nodes, may return an empty array
79     *
80     * @return AbstractNode[]
81     */
82    public function getChildren(): array
83    {
84        return $this->children;
85    }
86
87    /**
88     * Does this node have children?
89     *
90     * @return bool
91     */
92    public function hasChildren(): bool
93    {
94        return $this->children !== [];
95    }
96
97    /**
98     * Get all sub nodes and their sub nodes and so on
99     *
100     * @return AbstractNode[]
101     */
102    public function getDescendants(): array
103    {
104        $descendants = [];
105        foreach ($this->children as $child) {
106            $descendants[] = $child;
107            $descendants = array_merge($descendants, $child->getDescendants());
108        }
109        return $descendants;
110    }
111
112    /**
113     * Get all parent nodes in reverse order
114     *
115     * This list is cached, so it will only be calculated once.
116     *
117     * @return AbstractNode[]
118     */
119    public function getParents(): array
120    {
121        if (!$this->parents) {
122            $parent = $this->parent;
123            while ($parent) {
124                $this->parents[] = $parent;
125                $parent = $parent->getParent();
126            }
127        }
128
129        return $this->parents;
130    }
131
132    /**
133     * Set the direct parent node
134     */
135    public function setParent(AbstractNode $parent): void
136    {
137        $this->parent = $parent;
138    }
139
140    /**
141     * Get the direct parent node
142     *
143     * @return AbstractNode|null
144     */
145    public function getParent(): ?AbstractNode
146    {
147        return $this->parent;
148    }
149
150    /**
151     * @param AbstractNode $child
152     * @return void
153     */
154    public function addChild(AbstractNode $child): void
155    {
156        $child->setParent($this);
157        $this->children[] = $child;
158    }
159
160    /**
161     * Allows to attach an arbitrary property to the page
162     *
163     * @param string $name
164     * @param mixed $value
165     * @return void
166     */
167    public function setProperty(string $name, $value): void
168    {
169        $this->properties[$name] = $value;
170    }
171
172    /**
173     * Get the named property, default is returned when the property is not set
174     *
175     * @param string $name
176     * @param mixed $default
177     * @return mixed
178     */
179    public function getProperty(string $name, $default = null)
180    {
181        return $this->properties[$name] ?? $default;
182    }
183
184    /**
185     * Sort the children of this node and all its descendants
186     *
187     * The given comparator function will be called with two nodes as arguments and needs to
188     * return an integer less than, equal to, or greater than zero if the first argument is considered
189     * to be respectively less than, equal to, or greater than the second.
190     *
191     * @param callable $comparator
192     * @return void
193     */
194    public function sort(callable $comparator): void
195    {
196        usort($this->children, $comparator);
197        foreach ($this->children as $child) {
198            $child->sort($comparator);
199        }
200    }
201
202    /**
203     * Get the string representation of the node
204     *
205     * Uses plus char to show the depth of the node in the tree
206     *
207     * @return string
208     */
209    public function __toString(): string
210    {
211        return str_pad('', count($this->getParents()), '+') . $this->id;
212    }
213}
214