Completed
Pull Request — master (#339)
by William
01:30
created

class-wp-bootstrap-navwalker.php (9 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * WP Bootstrap Navwalker
4
 *
5
 * @package WP-Bootstrap-Navwalker
6
 */
7
8
/*
9
 * Class Name: WP_Bootstrap_Navwalker
10
 * Plugin Name: WP Bootstrap Navwalker
11
 * Plugin URI:  https://github.com/wp-bootstrap/wp-bootstrap-navwalker
12
 * Description: A custom WordPress nav walker class to implement the Bootstrap 4 navigation style in a custom theme using the WordPress built in menu manager.
13
 * Author: Edward McIntyre - @twittem, WP Bootstrap, William Patton - @pattonwebz
14
 * Version: 4.0.0
15
 * Author URI: https://github.com/wp-bootstrap
16
 * GitHub Plugin URI: https://github.com/wp-bootstrap/wp-bootstrap-navwalker
17
 * GitHub Branch: master
18
 * License: GPL-3.0+
19
 * License URI: http://www.gnu.org/licenses/gpl-3.0.txt
20
*/
21
22
/* Check if Class Exists. */
23
if ( ! class_exists( 'WP_Bootstrap_Navwalker' ) ) {
24
	/**
25
	 * WP_Bootstrap_Navwalker class.
26
	 *
27
	 * @extends Walker_Nav_Menu
28
	 */
29
	class WP_Bootstrap_Navwalker extends Walker_Nav_Menu {
30
31
		/**
32
		 * Starts the list before the elements are added.
33
		 *
34
		 * @since WP 3.0.0
35
		 *
36
		 * @see Walker_Nav_Menu::start_lvl()
37
		 *
38
		 * @param string   $output Used to append additional content (passed by reference).
39
		 * @param int      $depth  Depth of menu item. Used for padding.
40
		 * @param stdClass $args   An object of wp_nav_menu() arguments.
41
		 */
42
		public function start_lvl( &$output, $depth = 0, $args = array() ) {
43 View Code Duplication
			if ( isset( $args->item_spacing ) && 'discard' === $args->item_spacing ) {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
44
				$t = '';
45
				$n = '';
46
			} else {
47
				$t = "\t";
48
				$n = "\n";
49
			}
50
			$indent = str_repeat( $t, $depth );
51
			// Default class.
52
			$classes = array( 'dropdown-menu' );
53
			/**
54
			 * Filters the CSS class(es) applied to a menu list element.
55
			 *
56
			 * @since WP 4.8.0
57
			 *
58
			 * @param array    $classes The CSS classes that are applied to the menu `<ul>` element.
59
			 * @param stdClass $args    An object of `wp_nav_menu()` arguments.
60
			 * @param int      $depth   Depth of menu item. Used for padding.
61
			 */
62
			$class_names = join( ' ', apply_filters( 'nav_menu_submenu_css_class', $classes, $args, $depth ) );
63
			$class_names = $class_names ? ' class="' . esc_attr( $class_names ) . '"' : '';
64
			/**
65
			 * The `.dropdown-menu` container needs to have a labelledby
66
			 * attribute which points to it's trigger link.
67
			 *
68
			 * Form a string for the labelledby attribute from the the latest
69
			 * link with an id that was added to the $output.
70
			 */
71
			// find all links with an id in the output.
72
			preg_match_all( '/(<a.*?id=\"|\')(.*?)\"|\'.*?>/im', $output, $matches );
73
			// with pointer at end of array check if we got an ID match.
74
			if ( end( $matches[2] ) ) {
75
				// build a string to use as aria-labelledby.
76
				$lablledby = 'aria-labelledby="' . end( $matches[2] ) . '"';
77
			}
78
			$output .= "{$n}{$indent}<ul$class_names $lablledby role=\"menu\">{$n}";
0 ignored issues
show
The variable $lablledby does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
79
		}
80
81
		/**
82
		 * Starts the element output.
83
		 *
84
		 * @since WP 3.0.0
85
		 * @since WP 4.4.0 The {@see 'nav_menu_item_args'} filter was added.
86
		 *
87
		 * @see Walker_Nav_Menu::start_el()
88
		 *
89
		 * @param string   $output Used to append additional content (passed by reference).
90
		 * @param WP_Post  $item   Menu item data object.
91
		 * @param int      $depth  Depth of menu item. Used for padding.
92
		 * @param stdClass $args   An object of wp_nav_menu() arguments.
93
		 * @param int      $id     Current item ID.
94
		 */
95
		public function start_el( &$output, $item, $depth = 0, $args = array(), $id = 0 ) {
96 View Code Duplication
			if ( isset( $args->item_spacing ) && 'discard' === $args->item_spacing ) {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
97
				$t = '';
98
				$n = '';
0 ignored issues
show
$n is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
99
			} else {
100
				$t = "\t";
101
				$n = "\n";
0 ignored issues
show
$n is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
102
			}
103
			$indent = ( $depth ) ? str_repeat( $t, $depth ) : '';
104
105
			$value = '';
106
			$class_names = $value;
107
			$classes = empty( $item->classes ) ? array() : (array) $item->classes;
108
109
			// Initialize some holder variables to store specially handled item
110
			// wrappers and icons.
111
			$extra_link_classes = array();
112
			$icon_classes = array();
113
			$icon_class_string = '';
0 ignored issues
show
$icon_class_string is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
114
115
			// Loop and begin handling any special linkmod or icon classes.
116
			foreach ( $classes as $key => $class ) {
117
				/**
118
				 * Find any custom link mods or icons, store in their holder
119
				 * arrays and remove them from the classes array.
120
				 *
121
				 * Supported linkmods: .disabled, .dropdown-header, .dropdown-divider
122
				 * Supported iconsets: Font Awesome 4/5, Glypicons
123
				 */
124
				if ( preg_match( '/disabled/', $class ) ) {
125
					// Test for .disabled.
126
					$extra_link_classes[] = $class;
127
					unset( $classes[ $key ] );
128
				} elseif ( preg_match( '/dropdown-header|dropdown-divider/', $class ) && $depth > 0 ) {
129
					// Test for .dropdown-header or .dropdown-divider and a
130
					// depth greater than 0 - IE inside a dropdown.
131
					$extra_link_classes[] = $class;
132
					unset( $classes[ $key ] );
133
				} elseif ( preg_match( '/fa-(\S*)?|fas(\s?)|fa(\s?)/', $class ) ) {
134
					// Font Awesome.
135
					$icon_classes[] = $class;
136
					unset( $classes[ $key ] );
137
				} elseif ( preg_match( '/glyphicons-(\S*)?|glyphicons(\s?)/', $class ) ) {
138
					// Glyphicons.
139
					$icon_classes[] = $class;
140
					unset( $classes[ $key ] );
141
				}
142
			} // End foreach().
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
143
144
			// Join any icon classes plucked from $classes into a string.
145
			$icon_class_string = join( ' ', $icon_classes );
146
147
			/**
148
			 * Filters the arguments for a single nav menu item.
149
			 *
150
			 *  WP 4.4.0
151
			 *
152
			 * @param stdClass $args  An object of wp_nav_menu() arguments.
153
			 * @param WP_Post  $item  Menu item data object.
154
			 * @param int      $depth Depth of menu item. Used for padding.
155
			 */
156
			$args = apply_filters( 'nav_menu_item_args', $args, $item, $depth );
157
158
			$classes[] = 'menu-item-' . $item->ID;
159
			$classes[] = 'nav-item';
160
161
			// Add .dropdown or .active classes where they are needed.
162
			if ( $args->has_children ) {
163
				$classes[] = 'dropdown';
164
			}
165
			if ( in_array( 'current-menu-item', $classes, true ) || in_array( 'current-menu-parent', $classes, true ) ) {
166
				$classes = 'active';
167
			}
168
169
			// Allow filtering the classes.
170
			$classes = apply_filters( 'nav_menu_css_class', array_filter( $classes ), $item, $args, $depth );
171
172
			// Form a string of classes in format: class="class_names".
173
			$class_names = join( ' ', $classes );
174
			$class_names = $class_names ? ' class="' . esc_attr( $class_names ) . '"' : '';
175
176
			/**
177
			 * Filters the ID applied to a menu item's list item element.
178
			 *
179
			 * @since WP 3.0.1
180
			 * @since WP 4.1.0 The `$depth` parameter was added.
181
			 *
182
			 * @param string   $menu_id The ID that is applied to the menu item's `<li>` element.
183
			 * @param WP_Post  $item    The current menu item.
184
			 * @param stdClass $args    An object of wp_nav_menu() arguments.
185
			 * @param int      $depth   Depth of menu item. Used for padding.
186
			 */
187
			$id = apply_filters( 'nav_menu_item_id', 'menu-item-' . $item->ID, $item, $args, $depth );
188
			$id = $id ? ' id="' . esc_attr( $id ) . '"' : '';
189
190
			$output .= $indent . '<li itemscope="itemscope" itemtype="https://www.schema.org/SiteNavigationElement"' . $id . $value . $class_names . '>';
191
192
			// initialize array for holding the $atts for the link item.
193
			$atts = array();
194
195
			// Set title from item to the $atts array - if title is empty then
196
			// default to item title.
197
			if ( empty( $item->attr_title ) ) {
198
				$atts['title']  = ! empty( $item->title ) ? strip_tags( $item->title ) : '';
199
			} else {
200
				$atts['title'] = $item->attr_title;
201
			}
202
203
			$atts['target'] = ! empty( $item->target )	? $item->target	: '';
204
			$atts['rel']    = ! empty( $item->xfn )		? $item->xfn	: '';
205
			// If item has_children add atts to <a>.
206
			if ( $args->has_children && 0 === $depth && $args->depth > 1 ) {
207
				$atts['href']   		= '#';
208
				$atts['data-toggle']	= 'dropdown';
209
				$atts['aria-haspopup']	= 'true';
210
				$atts['aria-expanded']	= 'false';
211
				$atts['class']			= 'dropdown-toggle nav-link';
212
				$atts['id']				= 'menu-item-dropdown-' . $item->ID;
213
			} else {
214
				$atts['href'] 	= ! empty( $item->url ) ? $item->url : '';
215
				// Items in dropdowns use .dropdown-item instead of .nav-link.
216
				if ( $depth > 0 ) {
217
					$atts['class']	= 'dropdown-item';
218
				} else {
219
					$atts['class']	= 'nav-link';
220
				}
221
			}
222
223
			// Set this as an indetifier flag to ease identifying special item
224
			// types later in the processing. Default will be '' or 'link'.
225
			$type_flag = '';
226
			// Loop through the array of extra link classes plucked from the
227
			// parent <li>s classes array.
228
			if ( ! empty( $extra_link_classes ) ) {
229
				foreach ( $extra_link_classes as $link_class ) {
230
					if ( ! empty( $link_class ) ) {
231
						// update $atts with the extra classname.
232
						$atts['class'] .= ' ' . esc_attr( $link_class );
233
234
						// check for special class types we need additional handling for.
235
						if ( 'disabled' === $link_class ) {
236
							// Convert link to '#' and unset open targets.
237
							$atts['href'] = '#';
238
							unset( $atts['target'] );
239
						} elseif ( 'dropdown-header' === $link_class ) {
240
							// Store a type flag and unset href and target.
241
							$type_flag = 'dropdown-header';
242
							unset( $atts['href'] );
243
							unset( $atts['target'] );
244
						} elseif ( 'dropdown-divider' === $link_class ) {
245
							// Store a type flag and unset href and target.
246
							$type_flag = 'dropdown-divider';
247
							unset( $atts['href'] );
248
							unset( $atts['target'] );
249
						}
250
					}
251
				}
252
			}
253
254
			// Allow filtering of the $atts array before using it.
255
			$atts = apply_filters( 'nav_menu_link_attributes', $atts, $item, $args );
256
257
			// Build a string of html containing all the atts for the item.
258
			$attributes = '';
259
			foreach ( $atts as $attr => $value ) {
260
				if ( ! empty( $value ) ) {
261
					$value = ( 'href' === $attr ) ? esc_url( $value ) : esc_attr( $value );
262
					$attributes .= ' ' . $attr . '="' . $value . '"';
263
				}
264
			}
265
266
			/**
267
			 * START appending the internal item contents to the output.
268
			 */
269
			$item_output = $args->before;
270
271
			/**
272
			 * This is the start of the internal nav item. Depending on what
273
			 * kind of link mod we have we need different wrapper elements.
274
			 */
275
			if ( 'dropdown-header' === $type_flag ) {
276
				// For a header use a span with the .h6 class instead of a real
277
				// header tag so that it doesn't confuse screen readers.
278
				$item_output .= '<span class="dropdown-header h6"' . $attributes . '>';
279
			} elseif ( 'dropdown-divider' === $type_flag ) {
280
				// this is a divider.
281
				$item_output .= '<div class="dropdown-divider"' . $attributes . '>';
282
			} else {
283
				// With no link mod type set this must be a standard <a> tag.
284
				$item_output .= '<a' . $attributes . '>';
285
			}
286
287
			/**
288
			 * Initiate empty icon var, then if we have a string containing any
289
			 * icon classes form the icon markup with an <i> element. This is
290
			 * output inside of the item before the $title (the link text).
291
			 */
292
			$icon_html = '';
293
			if ( ! empty( $icon_class_string ) ) {
294
				// append an <i> with the icon classes to what is output before links.
295
				$icon_html = '<i class="' . esc_attr( $icon_class_string ) . '" aria-hidden="true"></i> ';
296
			}
297
298
			/** This filter is documented in wp-includes/post-template.php */
299
			$title = apply_filters( 'the_title', $item->title, $item->ID );
300
			/**
301
			 * Filters a menu item's title.
302
			 *
303
			 * @since 4.4.0
304
			 *
305
			 * @param string   $title The menu item's title.
306
			 * @param WP_Post  $item  The current menu item.
307
			 * @param stdClass $args  An object of wp_nav_menu() arguments.
308
			 * @param int      $depth Depth of menu item. Used for padding.
309
			 */
310
			$title = apply_filters( 'nav_menu_item_title', $title, $item, $args, $depth );
311
312
			// Put the item contents into $output.
313
			$item_output .= $args->link_before . $icon_html . $title . $args->link_after;
314
315
			/**
316
			 * This is the end of the internal nav item. We need to close the
317
			 * correct element depending on the type of link or link mod.
318
			 */
319
			if ( 'dropdown-header' === $type_flag ) {
320
				// this is a header.
321
				$item_output .= '</span>';
322
			} elseif ( 'dropdown-divider' === $type_flag ) {
323
				// this is a divider.
324
				$item_output .= '</div>';
325
			} else {
326
				// it's most likely a link at this point.
327
				$item_output .= '</a>';
328
			}
329
330
			$item_output .= $args->after;
331
			/**
332
			 * END appending the internal item contents to the output.
333
			 */
334
335
			$output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args );
336
337
		}
338
339
		/**
340
		 * Traverse elements to create list from elements.
341
		 *
342
		 * Display one element if the element doesn't have any children otherwise,
343
		 * display the element and its children. Will only traverse up to the max
344
		 * depth and no ignore elements under that depth. It is possible to set the
345
		 * max depth to include all depths, see walk() method.
346
		 *
347
		 * This method should not be called directly, use the walk() method instead.
348
		 *
349
		 * @since WP 2.5.0
350
		 *
351
		 * @see Walker::start_lvl()
352
		 *
353
		 * @param object $element           Data object.
354
		 * @param array  $children_elements List of elements to continue traversing (passed by reference).
355
		 * @param int    $max_depth         Max depth to traverse.
356
		 * @param int    $depth             Depth of current element.
357
		 * @param array  $args              An array of arguments.
358
		 * @param string $output            Used to append additional content (passed by reference).
359
		 */
360
		public function display_element( $element, &$children_elements, $max_depth, $depth, $args, &$output ) {
361
			if ( ! $element ) {
362
				return; }
363
			$id_field = $this->db_fields['id'];
364
			// Display this element.
365
			if ( is_object( $args[0] ) ) {
366
				$args[0]->has_children = ! empty( $children_elements[ $element->$id_field ] ); }
367
			parent::display_element( $element, $children_elements, $max_depth, $depth, $args, $output );
368
		}
369
370
		/**
371
		 * Menu Fallback
372
		 * =============
373
		 * If this function is assigned to the wp_nav_menu's fallback_cb variable
374
		 * and a menu has not been assigned to the theme location in the WordPress
375
		 * menu manager the function with display nothing to a non-logged in user,
376
		 * and will add a link to the WordPress menu manager if logged in as an admin.
377
		 *
378
		 * @param array $args passed from the wp_nav_menu function.
379
		 */
380
		public static function fallback( $args ) {
381
			if ( current_user_can( 'edit_theme_options' ) ) {
382
383
				/* Get Arguments. */
384
				$container = $args['container'];
385
				$container_id = $args['container_id'];
386
				$container_class = $args['container_class'];
387
				$menu_class = $args['menu_class'];
388
				$menu_id = $args['menu_id'];
389
390
				// initialize var to store fallback html.
391
				$fallback_output = '';
392
393
				if ( $container ) {
394
					$fallback_output .= '<' . esc_attr( $container );
395
					if ( $container_id ) {
396
						$fallback_output .= ' id="' . esc_attr( $container_id ) . '"';
397
					}
398
					if ( $container_class ) {
399
						$fallback_output .= ' class="' . esc_attr( $container_class ) . '"';
400
					}
401
					$fallback_output .= '>';
402
				}
403
				$fallback_output .= '<ul';
404
				if ( $menu_id ) {
405
					$fallback_output .= ' id="' . esc_attr( $menu_id ) . '"'; }
406
				if ( $menu_class ) {
407
					$fallback_output .= ' class="' . esc_attr( $menu_class ) . '"'; }
408
				$fallback_output .= '>';
409
				$fallback_output .= '<li><a href="' . esc_url( admin_url( 'nav-menus.php' ) ) . '" title="">' . esc_attr( 'Add a menu', '' ) . '</a></li>';
410
				$fallback_output .= '</ul>';
411
				if ( $container ) {
412
					$fallback_output .= '</' . esc_attr( $container ) . '>';
413
				}
414
415
				// if $args has 'echo' key and it's true echo, otherwise return.
416
				if ( array_key_exists( 'echo', $args ) && $args['echo'] ) {
417
					echo $fallback_output; // WPCS: XSS OK.
418
				} else {
419
					return $fallback_output;
420
				}
421
			} // End if().
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
422
		}
423
	}
424
} // End if().
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
425