Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
78.57% covered (success)
78.57%
99 / 126
50.00% covered (warning)
50.00%
1 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
NavBuilder
78.57% covered (success)
78.57%
99 / 126
50.00% covered (warning)
50.00%
1 / 2
158.77
0.00% covered (danger)
0.00%
0 / 1
 build
70.65% covered (success)
70.65%
65 / 92
0.00% covered (danger)
0.00%
0 / 1
146.99
 prepare
100.00% covered (success)
100.00%
34 / 34
100.00% covered (success)
100.00%
1 / 1
27
1<?php
2/**
3 * Pop PHP Framework (http://www.popphp.org/)
4 *
5 * @link       https://github.com/popphp/popphp-framework
6 * @author     Nick Sagona, III <dev@nolainteractive.com>
7 * @copyright  Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com)
8 * @license    http://www.popphp.org/license     New BSD License
9 */
10
11/**
12 * @namespace
13 */
14namespace Pop\Nav;
15
16use Pop\Dom\Child;
17
18/**
19 * Nav builder class
20 *
21 * @category   Pop
22 * @package    Pop\Nav
23 * @author     Nick Sagona, III <dev@nolainteractive.com>
24 * @copyright  Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com)
25 * @license    http://www.popphp.org/license     New BSD License
26 * @version    3.3.0
27 */
28class 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 === false) {
80                            $allowed = false;
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}