Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
85.71% covered (success)
85.71%
96 / 112
50.00% covered (warning)
50.00%
1 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
NavBuilder
85.71% covered (success)
85.71%
96 / 112
50.00% covered (warning)
50.00%
1 / 2
95.74
0.00% covered (danger)
0.00%
0 / 1
 build
79.49% covered (success)
79.49%
62 / 78
0.00% covered (danger)
0.00%
0 / 1
73.45
 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        [$nav, $child] = self::prepare($navObject, $config, $depth);
45
46        $navObject->incrementParentLevel();
47        $depth++;
48
49        // Recursively loop through the nodes
50        foreach ($tree as $node) {
51            $allowed = true;
52            if (isset($node['acl'])) {
53                if ($navObject->getAcl() === null) {
54                    throw new Exception('The access control object is not set.');
55                }
56                if (empty($navObject->getRoles())) {
57                    $allowed = false;
58                } else {
59                    $resource   = (isset($node['acl']['resource'])) ? $node['acl']['resource'] : null;
60                    $permission = (isset($node['acl']['permission'])) ? $node['acl']['permission'] : null;
61                    $allowed    = ($navObject->isAclStrict()) ?
62                        $navObject->getAcl()->isAllowedManyStrict($navObject->getRoles(), $resource, $permission) :
63                        $navObject->getAcl()->isAllowedMany($navObject->getRoles(), $resource, $permission);
64                }
65            }
66            if (($allowed) && isset($node['name']) && isset($node['href'])) {
67                // Create child node and child link node
68                $a = new Child('a', $node['name']);
69
70                if ((str_starts_with($node['href'], '#')) || (str_ends_with($node['href'], '#')) ||
71                    (str_starts_with($node['href'], 'http')) || (str_starts_with($node['href'], 'mailto:'))) {
72                    $href = $node['href'];
73                } else if (str_starts_with($node['href'], '/')) {
74                    $href = $navObject->getBaseUrl() . $node['href'];
75                } else {
76                    if (str_ends_with($parentHref, '/')) {
77                        $href = $parentHref . $node['href'];
78                    } else {
79                        $href = $parentHref . '/' . $node['href'];
80                    }
81                }
82
83                $a->setAttribute('href', $href);
84
85                if (($navObject->isReturnFalse()) && (($href == '#') || (str_ends_with($href, '#')))) {
86                    $a->setAttribute('onclick', 'return false;');
87                }
88                $url = $_SERVER['REQUEST_URI'] ?? null;
89                if (str_contains($url, '?')) {
90                    $url = substr($url, strpos($url, '?'));
91                }
92
93                $linkClass = null;
94                if ($href == $url) {
95                    if (isset($config['on'])) {
96                        $linkClass = $config['on'];
97                    }
98                } else {
99                    if (isset($config['off'])) {
100                        $linkClass = $config['off'];
101                    }
102                }
103
104                // If the node has any attributes
105                if (isset($node['attributes'])) {
106                    foreach ($node['attributes'] as $attrib => $value) {
107                        $value = (($attrib == 'class') && ($linkClass !== null)) ? $value . ' ' . $linkClass : $value;
108                        $a->setAttribute($attrib, $value);
109                    }
110                } else if ($linkClass !== null) {
111                    $a->setAttribute('class', $linkClass);
112                }
113
114                if ($child !== null) {
115                    $navChild = new Child($child);
116
117                    // Set child attributes if they exist
118                    if (isset($config['child']) && isset($config['child']['id'])) {
119                        $navChild->setAttribute('id', $config['child']['id'] . '-' . $navObject->getChildLevel());
120                    }
121                    if (isset($config['child']) && isset($config['child']['class'])) {
122                        $navChild->setAttribute('class', $config['child']['class'] . '-' . ($depth - 1));
123                    }
124                    if (isset($config['child']['attributes'])) {
125                        foreach ($config['child']['attributes'] as $attrib => $value) {
126                            $navChild->setAttribute($attrib, $value);
127                        }
128                    }
129
130                    // Add link node
131                    $navChild->addChild($a);
132                    $navObject->incrementChildLevel();
133
134                    // If there are children, loop through and add them
135                    if (isset($node['children']) && is_array($node['children']) && (count($node['children']) > 0)) {
136                        $childrenAllowed = true;
137                        // Check if the children are allowed
138
139                        $i = 0;
140                        foreach ($node['children'] as $nodeChild) {
141                            if (isset($nodeChild['acl'])) {
142                                if ($navObject->getAcl() === null) {
143                                    throw new Exception('The access control object is not set.');
144                                }
145                                if (empty($navObject->getRoles())) {
146                                    $childrenAllowed = false;
147                                } else {
148                                    $resource   = (isset($nodeChild['acl']['resource'])) ? $nodeChild['acl']['resource'] : null;
149                                    $permission = (isset($nodeChild['acl']['permission'])) ? $nodeChild['acl']['permission'] : null;
150                                    $method     = ($navObject->isAclStrict()) ? 'isAllowedManyStrict' : 'isAllowedMany';
151                                    if (!($navObject->getAcl()->{$method}($navObject->getRoles(), $resource, $permission))) {
152                                        $i++;
153                                    }
154                                }
155                            }
156                        }
157                        if ($i == count($node['children'])) {
158                            $childrenAllowed = false;
159                        }
160                        if ($childrenAllowed) {
161                            $nextChild = self::build($navObject, $node['children'], $depth, $href);
162                            if (($nextChild->hasChildren()) || ($nextChild->getNodeValue() !== null)) {
163                                $navChild->addChild($nextChild);
164                            }
165                        }
166                    }
167                    // Add child node
168                    $nav->addChild($navChild);
169                } else {
170                    $nav->addChild($a);
171                }
172            }
173        }
174
175        return $nav;
176    }
177
178    /**
179     * Prepare nav node
180     *
181     * @param  Nav   $navObject
182     * @param  array $config
183     * @param  int   $depth
184     * @return array
185     */
186    public static function prepare(Nav $navObject, array $config, int $depth = 1): array
187    {
188        // Create overriding top level parent, if set
189        if (($depth == 1) && isset($config['top'])) {
190            $parent = (isset($config['top']) && isset($config['top']['node'])) ? $config['top']['node'] : 'nav';
191            $child  = null;
192            if (isset($config['child']) && isset($config['child']['node'])) {
193                $child = $config['child']['node'];
194            } else if ($parent == 'nav') {
195                $child = 'nav';
196            }
197
198            // Create parent node
199            $nav = new Child($parent);
200            if ($navObject->getIndent() !== null) {
201                $nav->setIndent(str_repeat($navObject->getIndent(), $depth));
202            }
203
204            // Set top attributes if they exist
205            if (isset($config['top']) && isset($config['top']['id'])) {
206                $nav->setAttribute('id', $config['top']['id']);
207            }
208            if (isset($config['top']) && isset($config['top']['class'])) {
209                $nav->setAttribute('class', $config['top']['class']);
210            }
211            if (isset($config['top']['attributes'])) {
212                foreach ($config['top']['attributes'] as $attrib => $value) {
213                    $nav->setAttribute($attrib, $value);
214                }
215            }
216        } else {
217            // Set up parent/child node names
218            $parent = (isset($config['parent']) && isset($config['parent']['node'])) ? $config['parent']['node'] : 'nav';
219            $child  = null;
220            if (isset($config['child']) && isset($config['child']['node'])) {
221                $child = $config['child']['node'];
222            } else if ($parent == 'nav') {
223                $child = 'nav';
224            }
225
226            // Create parent node
227            $nav = new Child($parent);
228            if ($navObject->getIndent() !== null) {
229                $nav->setIndent(str_repeat($navObject->getIndent(), $depth));
230            }
231
232            // Set parent attributes if they exist
233            if (isset($config['parent']) && isset($config['parent']['id'])) {
234                $nav->setAttribute('id', $config['parent']['id'] . '-' . $navObject->getParentLevel());
235            }
236            if (isset($config['parent']) && isset($config['parent']['class'])) {
237                $nav->setAttribute('class', $config['parent']['class'] . '-' . $depth);
238            }
239            if (isset($config['parent']['attributes'])) {
240                foreach ($config['parent']['attributes'] as $attrib => $value) {
241                    $nav->setAttribute($attrib, $value);
242                }
243            }
244        }
245
246        return [$nav, $child];
247    }
248
249}