fi_protie_navigation   A
last analyzed

Complexity

Total Complexity 41

Size/Duplication

Total Lines 268
Duplicated Lines 0 %

Test Coverage

Coverage 24.05%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 95
dl 0
loc 268
ccs 19
cts 79
cp 0.2405
rs 9.1199
c 2
b 0
f 0
wmc 41

7 Methods

Rating   Name   Duplication   Size   Complexity  
A get_node_path() 0 8 2
A _list_child_elements() 0 30 6
A draw() 0 18 6
B _display_element() 0 24 9
A set_root_element_id() 0 3 1
A __construct() 0 7 2
C _get_css_classes() 0 43 15

How to fix   Complexity   

Complex Class

Complex classes like fi_protie_navigation often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use fi_protie_navigation, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * @package fi.protie.navigation
4
 * @author The Midgard Project, http://www.midgard-project.org
5
 * @copyright The Midgard Project, http://www.midgard-project.org
6
 * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License
7
 */
8
9
/**
10
 * Versatile class for drawing dynamically navigation elements according to
11
 * user preferences.
12
 *
13
 * <code>
14
 * // Initializes the class
15
 * $navigation = new fi_protie_navigation();
16
 *
17
 * // Display only nodes (folders)
18
 * $navigation->list_leaves = false;
19
 *
20
 * // Expand the whole site tree instead of the active path
21
 * $navigation->follow_all = true;
22
 *
23
 * // Skip 1 level from the beginning of the active path
24
 * $navigation->skip_levels = 1;
25
 *
26
 * // Finally draw the navigation
27
 * $navigation->draw();
28
 * </code>
29
 *
30
 * See the attributes of this class for additional customizing options.
31
 *
32
 * @package fi.protie.navigation
33
 */
34
class fi_protie_navigation
35
{
36
    /**
37
     * MidCOM helper class for navigation subsystem. Uses class 'midcom.helper.nav'
38
     */
39
    private midcom_helper_nav $_nap;
40
41
    /**
42
     * Stores the navigation access point history or in other words path to the current point.
43
     */
44
    private array $node_path = [];
45
46
    /**
47
     * ID for the folder to get the navigation
48
     */
49
    public ?int $root_id = null;
50
51
    /**
52
     * Number of the parsed level
53
     */
54
    private int $_level = 1;
55
56
    /**
57
     * The amount of lowest level elements to be skipped.
58
     */
59
    public int $skip_levels = 0;
60
61
    /**
62
     * Switch to determine if navigation should display leaves or pages.
63
     */
64
    public bool $list_leaves = true;
65
66
    /**
67
     * List only the leaf elements or pages
68
     */
69
    public bool $list_nodes = true;
70
71
    /**
72
     * Switch to determine if navigation should follow node path (on true) or stop on the
73
     * spot.
74
     */
75
    public bool $follow_selected = true;
76
77
    /**
78
     * Switch to determine if navigation should follow all the nodes or only the current
79
     */
80
    public bool $follow_all = false;
81
82
    /**
83
     * Restrict the amount of levels listed.
84
     */
85
    public int $list_levels = 0;
86
87
    /**
88
     * ID of the root level list object
89
     */
90
    public ?string $root_object_id = null;
91
92
    /**
93
     * CSS class for styling the lists
94
     */
95
    public string $css_list_style = 'fi_protie_navigation';
96
97
    /**
98
     * Add component name to list item ul class name
99
     */
100
    public bool $component_name_to_class = false;
101
102
    /**
103
     * Check if item has children and if so, add node/leaf class to list item
104
     */
105
    public bool $has_children_to_class = false;
106
107
    /**
108
     * Should the object's status be added to list item ul class names
109
     * Since this forces us to load the entire object, set it to false if you don't need it
110
     */
111
    public bool $object_status_to_class = false;
112
113
    /**
114
     * CSS class for nodes
115
     */
116
    public string $css_node = 'node';
117
118
    /**
119
     * CSS class for leaves
120
     */
121
    public string $css_leaf = 'leaf';
122
123
    /**
124
     * CSS class for the elements in node path. All the elements in node path will have this class.
125
     */
126
    public string $css_selected = 'selected';
127
128
    /**
129
     * CSS class for the current, active node or leaf. There can be only one active element.
130
     */
131
    public string $css_active = 'active';
132
133
    /**
134
     * CSS class for links
135
     */
136
    public string $css_link = 'link';
137
138
    /**
139
     * Here we initialize the classes and variables needed through the class.
140
     */
141 9
    public function __construct(?int $id = null)
142
    {
143 9
        $this->_nap = new midcom_helper_nav();
144 9
        $this->get_node_path();
145
146 9
        if ($id !== null) {
147
            $this->root_id = $id;
148
        }
149
    }
150
151
    /**
152
     * Traverses through the node path to fetch the location of the current navigation access point.
153
     */
154 9
    private function get_node_path()
155
    {
156
        // Get nodes
157 9
        $this->node_path = $this->_nap->get_node_path();
158
159
        // If NAP offers a leaf it should be stored in the node path
160 9
        if ($leaf = $this->_nap->get_current_leaf()) {
161
            $this->node_path[] = $leaf;
162
        }
163
    }
164
165
    /**
166
     * Traverse the child elements starting from the requested node id
167
     */
168 9
    private function _list_child_elements(int $id)
169
    {
170 9
        if (!$this->list_leaves) {
171
            $children = $this->_nap->get_nodes($id);
172 9
        } elseif (!$this->list_nodes) {
173
            $children = $this->_nap->get_leaves($id);
174
        } else {
175 9
            $children = $this->_nap->list_child_elements($id);
176
        }
177
178
        // Stop traversing the path if there are no children
179 9
        if (!$children) {
180 9
            return;
181
        }
182
183
        // Add ID property to the first unordered list ever called
184
        $element_id = '';
185
        if ($this->root_object_id) {
186
            $element_id = " id=\"{$this->root_object_id}\"";
187
            $this->root_object_id = null;
188
        }
189
190
        echo "<ul class=\"{$this->css_list_style} node-{$id}\"{$element_id}>";
191
192
        // Draw each child element
193
        foreach ($children as $child) {
194
            $this->_display_element($child);
195
        }
196
197
        echo "</ul>";
198
    }
199
200
    private function _get_css_classes(array $item) : string
201
    {
202
        $classes = [];
203
204
        if ($item[MIDCOM_NAV_TYPE] === 'node') {
205
            if (   $item[MIDCOM_NAV_ID] === $this->_nap->get_current_node()
206
                && (   !$this->_nap->get_current_leaf()
207
                    || !$this->_nap->get_leaf($this->_nap->get_current_leaf()))) {
208
                $classes[] = $this->css_active;
209
            }
210
211
            if (in_array($item[MIDCOM_NAV_ID], $this->node_path, true)) {
212
                $classes[] = $this->css_selected;
213
            }
214
215
            if ($this->component_name_to_class) {
216
                $classes[] = str_replace('.', '_', $item[MIDCOM_NAV_COMPONENT]);
217
            }
218
        } elseif ($item[MIDCOM_NAV_ID] === $this->_nap->get_current_leaf()) {
219
            // Place the corresponding css class for the currently active leaf)
220
            $classes[] = $this->css_active;
221
            $classes[] = $this->css_selected;
222
        }
223
224
        if ($this->has_children_to_class) {
225
            if (!$this->list_leaves) {
226
                $children = $this->_nap->get_nodes($item[MIDCOM_NAV_ID]);
227
            } elseif ($item[MIDCOM_NAV_TYPE] == 'node') {
228
                $children = $this->_nap->list_child_elements($item[MIDCOM_NAV_ID]);
229
            } else {
230
                $children = false;
231
            }
232
            $classes[] = $children ? $this->css_node : $this->css_leaf;
233
        }
234
235
        // Add information about the object's status
236
        if (   $this->object_status_to_class
237
            && isset($item[MIDCOM_NAV_OBJECT])
238
            && $css_status_class = midcom::get()->metadata->get_object_classes($item[MIDCOM_NAV_OBJECT])) {
239
            $classes[] = $css_status_class;
240
        }
241
242
        return implode(' ', $classes);
243
    }
244
245
    private function _display_element(array $item)
246
    {
247
        $css_classes = $this->_get_css_classes($item);
248
        // Finalize the class naming
249
        $class = ($css_classes !== '') ? ' class="' . $css_classes . '"' : '';
250
        $link_class = $this->css_link ? ' class="' . $this->css_link . '"' : '';
251
252
        echo "<li{$class}>";
253
        echo "<a href=\"{$item[MIDCOM_NAV_ABSOLUTEURL]}\"{$link_class}>" . htmlspecialchars($item[MIDCOM_NAV_NAME]) . "</a>";
254
        // If either of the follow nodes switches is on, follow all the nodes
255
256
        if (   $item[MIDCOM_NAV_TYPE] === 'node'
257
            && (   $this->list_levels === 0
258
                || $this->_level < $this->list_levels)) {
259
            if (   $this->follow_all
260
                || (   $this->follow_selected
261
                    && in_array($item[MIDCOM_NAV_ID], $this->node_path, true))) {
262
                $this->_level++;
263
                $this->_list_child_elements($item[MIDCOM_NAV_ID]);
264
                $this->_level--;
265
            }
266
        }
267
268
        echo "</li>";
269
    }
270
271
    /**
272
     * Draw the navigation.
273
     */
274 9
    public function draw()
275
    {
276 9
        if (!$this->list_leaves && !$this->list_nodes) {
277
            return;
278
        }
279 9
        if (!$this->root_id) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->root_id of type integer|null is loosely compared to false; this is ambiguous if the integer can be 0. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
280 9
            $this->root_id = $this->_nap->get_root_node();
281
        }
282
283 9
        if ($this->skip_levels !== 0) {
284
            if (!array_key_exists($this->skip_levels, $this->node_path)) {
285
                return;
286
            }
287
288
            $this->root_id = $this->node_path[$this->skip_levels];
289
        }
290
291 9
        $this->_list_child_elements($this->root_id);
292
    }
293
294
    /**
295
     * Set the root element id
296
     *
297
     * @param string $id root ul id
298
     */
299
    public function set_root_element_id(string $id)
300
    {
301
        $this->root_object_id = $id;
302
    }
303
}
304