Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
78.57% |
99 / 126 |
|
50.00% |
1 / 2 |
CRAP | |
0.00% |
0 / 1 |
NavBuilder | |
78.57% |
99 / 126 |
|
50.00% |
1 / 2 |
158.77 | |
0.00% |
0 / 1 |
build | |
70.65% |
65 / 92 |
|
0.00% |
0 / 1 |
146.99 | |||
prepare | |
100.00% |
34 / 34 |
|
100.00% |
1 / 1 |
27 |
1 | <?php |
2 | /** |
3 | * Pop PHP Framework (https://www.popphp.org/) |
4 | * |
5 | * @link https://github.com/popphp/popphp-framework |
6 | * @author Nick Sagona, III <dev@noladev.com> |
7 | * @copyright Copyright (c) 2009-2025 NOLA Interactive, LLC. |
8 | * @license https://www.popphp.org/license New BSD License |
9 | */ |
10 | |
11 | /** |
12 | * @namespace |
13 | */ |
14 | namespace Pop\Nav; |
15 | |
16 | use Pop\Dom\Child; |
17 | |
18 | /** |
19 | * Nav builder class |
20 | * |
21 | * @category Pop |
22 | * @package Pop\Nav |
23 | * @author Nick Sagona, III <dev@noladev.com> |
24 | * @copyright Copyright (c) 2009-2025 NOLA Interactive, LLC. |
25 | * @license https://www.popphp.org/license New BSD License |
26 | * @version 4.1.4 |
27 | */ |
28 | class NavBuilder |
29 | { |
30 | |
31 | /** |
32 | * Build the navigation from the nav object |
33 | * |
34 | * @param Nav $navObject |
35 | * @param array $tree |
36 | * @param int $depth |
37 | * @param ?string $parentHref |
38 | * @throws Exception|\Pop\Acl\Exception |
39 | * @return Child |
40 | */ |
41 | public static function build(Nav $navObject, array $tree, int $depth = 1, ?string $parentHref = null): Child |
42 | { |
43 | $config = $navObject->getConfig(); |
44 | $globalPolicy = $config['policy'] ?? null; |
45 | [$nav, $child] = self::prepare($navObject, $config, $depth); |
46 | |
47 | $navObject->incrementParentLevel(); |
48 | $depth++; |
49 | |
50 | // Recursively loop through the nodes |
51 | foreach ($tree as $node) { |
52 | $allowed = true; |
53 | if (isset($node['acl'])) { |
54 | if ($navObject->getAcl() === null) { |
55 | throw new Exception('The access control object is not set.'); |
56 | } |
57 | if (empty($navObject->getRoles())) { |
58 | $allowed = false; |
59 | } else { |
60 | $resource = (isset($node['acl']['resource'])) ? $node['acl']['resource'] : null; |
61 | $permission = (isset($node['acl']['permission'])) ? $node['acl']['permission'] : null; |
62 | $policy = (isset($node['acl']['policy'])) ? $node['acl']['policy'] : $globalPolicy; |
63 | $allowed = ($navObject->isAclStrict()) ? |
64 | $navObject->getAcl()->isAllowedMultiStrict($navObject->getRoles(), $resource, $permission) : |
65 | $navObject->getAcl()->isAllowedMulti($navObject->getRoles(), $resource, $permission); |
66 | |
67 | if (!empty($policy)) { |
68 | if ($policy instanceof \Pop\Utils\CallableObject) { |
69 | $policy = $policy->call(); |
70 | } else if (is_callable($policy)) { |
71 | $policy = call_user_func($policy); |
72 | } else if (is_array($policy) && isset($policy[0]) && is_callable($policy[0])) { |
73 | $callable = $policy[0]; |
74 | unset($policy[0]); |
75 | $policy = call_user_func_array($callable, array_values($policy)); |
76 | } |
77 | |
78 | $policyResult = $navObject->getAcl()->evaluatePolicy($permission, $policy, $resource); |
79 | if ($policyResult !== null) { |
80 | $allowed = $policyResult; |
81 | } |
82 | } |
83 | } |
84 | } |
85 | if (($allowed) && isset($node['name']) && isset($node['href'])) { |
86 | // Create child node and child link node |
87 | $a = new Child('a', $node['name']); |
88 | |
89 | if ((str_starts_with($node['href'], '#')) || (str_ends_with($node['href'], '#')) || |
90 | (str_starts_with($node['href'], 'http')) || (str_starts_with($node['href'], 'mailto:'))) { |
91 | $href = $node['href']; |
92 | } else if (str_starts_with($node['href'], '/')) { |
93 | $href = $navObject->getBaseUrl() . $node['href']; |
94 | } else { |
95 | if (str_ends_with($parentHref, '/')) { |
96 | $href = $parentHref . $node['href']; |
97 | } else { |
98 | $href = $parentHref . '/' . $node['href']; |
99 | } |
100 | } |
101 | |
102 | $a->setAttribute('href', $href); |
103 | |
104 | if (($navObject->isReturnFalse()) && (($href == '#') || (str_ends_with($href, '#')))) { |
105 | $a->setAttribute('onclick', 'return false;'); |
106 | } |
107 | $url = $_SERVER['REQUEST_URI'] ?? null; |
108 | if (str_contains($url, '?')) { |
109 | $url = substr($url, strpos($url, '?')); |
110 | } |
111 | |
112 | $linkClass = null; |
113 | if ($href == $url) { |
114 | if (isset($config['on'])) { |
115 | $linkClass = $config['on']; |
116 | } |
117 | } else { |
118 | if (isset($config['off'])) { |
119 | $linkClass = $config['off']; |
120 | } |
121 | } |
122 | |
123 | // If the node has any attributes |
124 | if (isset($node['attributes'])) { |
125 | foreach ($node['attributes'] as $attrib => $value) { |
126 | $value = (($attrib == 'class') && ($linkClass !== null)) ? $value . ' ' . $linkClass : $value; |
127 | $a->setAttribute($attrib, $value); |
128 | } |
129 | } else if ($linkClass !== null) { |
130 | $a->setAttribute('class', $linkClass); |
131 | } |
132 | |
133 | if ($child !== null) { |
134 | $navChild = new Child($child); |
135 | |
136 | // Set child attributes if they exist |
137 | if (isset($config['child']) && isset($config['child']['id'])) { |
138 | $navChild->setAttribute('id', $config['child']['id'] . '-' . $navObject->getChildLevel()); |
139 | } |
140 | if (isset($config['child']) && isset($config['child']['class'])) { |
141 | $navChild->setAttribute('class', $config['child']['class'] . '-' . ($depth - 1)); |
142 | } |
143 | if (isset($config['child']['attributes'])) { |
144 | foreach ($config['child']['attributes'] as $attrib => $value) { |
145 | $navChild->setAttribute($attrib, $value); |
146 | } |
147 | } |
148 | |
149 | // Add link node |
150 | $navChild->addChild($a); |
151 | $navObject->incrementChildLevel(); |
152 | |
153 | // If there are children, loop through and add them |
154 | if (isset($node['children']) && is_array($node['children']) && (count($node['children']) > 0)) { |
155 | $childrenAllowed = true; |
156 | // Check if the children are allowed |
157 | |
158 | $i = 0; |
159 | foreach ($node['children'] as $nodeChild) { |
160 | if (isset($nodeChild['acl'])) { |
161 | if ($navObject->getAcl() === null) { |
162 | throw new Exception('The access control object is not set.'); |
163 | } |
164 | if (empty($navObject->getRoles())) { |
165 | $childrenAllowed = false; |
166 | } else { |
167 | $resource = (isset($nodeChild['acl']['resource'])) ? $nodeChild['acl']['resource'] : null; |
168 | $permission = (isset($nodeChild['acl']['permission'])) ? $nodeChild['acl']['permission'] : null; |
169 | $method = ($navObject->isAclStrict()) ? 'isAllowedMultiStrict' : 'isAllowedMulti'; |
170 | if (!($navObject->getAcl()->{$method}($navObject->getRoles(), $resource, $permission))) { |
171 | $i++; |
172 | } |
173 | } |
174 | } |
175 | } |
176 | if ($i == count($node['children'])) { |
177 | $childrenAllowed = false; |
178 | } |
179 | if ($childrenAllowed) { |
180 | $nextChild = self::build($navObject, $node['children'], $depth, $href); |
181 | if (($nextChild->hasChildren()) || ($nextChild->getNodeValue() !== null)) { |
182 | $navChild->addChild($nextChild); |
183 | } |
184 | } |
185 | } |
186 | // Add child node |
187 | $nav->addChild($navChild); |
188 | } else { |
189 | $nav->addChild($a); |
190 | } |
191 | } |
192 | } |
193 | |
194 | return $nav; |
195 | } |
196 | |
197 | /** |
198 | * Prepare nav node |
199 | * |
200 | * @param Nav $navObject |
201 | * @param array $config |
202 | * @param int $depth |
203 | * @return array |
204 | */ |
205 | public static function prepare(Nav $navObject, array $config, int $depth = 1): array |
206 | { |
207 | // Create overriding top level parent, if set |
208 | if (($depth == 1) && isset($config['top'])) { |
209 | $parent = (isset($config['top']) && isset($config['top']['node'])) ? $config['top']['node'] : 'nav'; |
210 | $child = null; |
211 | if (isset($config['child']) && isset($config['child']['node'])) { |
212 | $child = $config['child']['node']; |
213 | } else if ($parent == 'nav') { |
214 | $child = 'nav'; |
215 | } |
216 | |
217 | // Create parent node |
218 | $nav = new Child($parent); |
219 | if ($navObject->getIndent() !== null) { |
220 | $nav->setIndent(str_repeat($navObject->getIndent(), $depth)); |
221 | } |
222 | |
223 | // Set top attributes if they exist |
224 | if (isset($config['top']) && isset($config['top']['id'])) { |
225 | $nav->setAttribute('id', $config['top']['id']); |
226 | } |
227 | if (isset($config['top']) && isset($config['top']['class'])) { |
228 | $nav->setAttribute('class', $config['top']['class']); |
229 | } |
230 | if (isset($config['top']['attributes'])) { |
231 | foreach ($config['top']['attributes'] as $attrib => $value) { |
232 | $nav->setAttribute($attrib, $value); |
233 | } |
234 | } |
235 | } else { |
236 | // Set up parent/child node names |
237 | $parent = (isset($config['parent']) && isset($config['parent']['node'])) ? $config['parent']['node'] : 'nav'; |
238 | $child = null; |
239 | if (isset($config['child']) && isset($config['child']['node'])) { |
240 | $child = $config['child']['node']; |
241 | } else if ($parent == 'nav') { |
242 | $child = 'nav'; |
243 | } |
244 | |
245 | // Create parent node |
246 | $nav = new Child($parent); |
247 | if ($navObject->getIndent() !== null) { |
248 | $nav->setIndent(str_repeat($navObject->getIndent(), $depth)); |
249 | } |
250 | |
251 | // Set parent attributes if they exist |
252 | if (isset($config['parent']) && isset($config['parent']['id'])) { |
253 | $nav->setAttribute('id', $config['parent']['id'] . '-' . $navObject->getParentLevel()); |
254 | } |
255 | if (isset($config['parent']) && isset($config['parent']['class'])) { |
256 | $nav->setAttribute('class', $config['parent']['class'] . '-' . $depth); |
257 | } |
258 | if (isset($config['parent']['attributes'])) { |
259 | foreach ($config['parent']['attributes'] as $attrib => $value) { |
260 | $nav->setAttribute($attrib, $value); |
261 | } |
262 | } |
263 | } |
264 | |
265 | return [$nav, $child]; |
266 | } |
267 | |
268 | } |