Issues (2010)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

wp-includes/nav-menu.php (7 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
 * Navigation Menu functions
4
 *
5
 * @package WordPress
6
 * @subpackage Nav_Menus
7
 * @since 3.0.0
8
 */
9
10
/**
11
 * Returns a navigation menu object.
12
 *
13
 * @since 3.0.0
14
 *
15
 * @param string $menu Menu ID, slug, or name - or the menu object.
16
 * @return object|false False if $menu param isn't supplied or term does not exist, menu object if successful.
17
 */
18
function wp_get_nav_menu_object( $menu ) {
19
	$menu_obj = false;
20
21
	if ( is_object( $menu ) ) {
22
		$menu_obj = $menu;
23
	}
24
25
	if ( $menu && ! $menu_obj ) {
26
		$menu_obj = get_term( $menu, 'nav_menu' );
27
28
		if ( ! $menu_obj ) {
29
			$menu_obj = get_term_by( 'slug', $menu, 'nav_menu' );
30
		}
31
32
		if ( ! $menu_obj ) {
33
			$menu_obj = get_term_by( 'name', $menu, 'nav_menu' );
34
		}
35
	}
36
37
	if ( ! $menu_obj || is_wp_error( $menu_obj ) ) {
38
		$menu_obj = false;
39
	}
40
41
	/**
42
	 * Filters the nav_menu term retrieved for wp_get_nav_menu_object().
43
	 *
44
	 * @since 4.3.0
45
	 *
46
	 * @param object|false $menu_obj Term from nav_menu taxonomy, or false if nothing had been found.
47
	 * @param string       $menu     The menu ID, slug, or name passed to wp_get_nav_menu_object().
48
	 */
49
	return apply_filters( 'wp_get_nav_menu_object', $menu_obj, $menu );
50
}
51
52
/**
53
 * Check if the given ID is a navigation menu.
54
 *
55
 * Returns true if it is; false otherwise.
56
 *
57
 * @since 3.0.0
58
 *
59
 * @param int|string $menu The menu to check (ID, slug, or name).
60
 * @return bool Whether the menu exists.
61
 */
62
function is_nav_menu( $menu ) {
63
	if ( ! $menu )
64
		return false;
65
66
	$menu_obj = wp_get_nav_menu_object( $menu );
67
68
	if (
69
		$menu_obj &&
70
		! is_wp_error( $menu_obj ) &&
71
		! empty( $menu_obj->taxonomy ) &&
72
		'nav_menu' == $menu_obj->taxonomy
73
	)
74
		return true;
75
76
	return false;
77
}
78
79
/**
80
 * Registers navigation menu locations for a theme.
81
 *
82
 * @since 3.0.0
83
 *
84
 * @global array $_wp_registered_nav_menus
85
 *
86
 * @param array $locations Associative array of menu location identifiers (like a slug) and descriptive text.
87
 */
88
function register_nav_menus( $locations = array() ) {
89
	global $_wp_registered_nav_menus;
90
91
	add_theme_support( 'menus' );
92
93
	$_wp_registered_nav_menus = array_merge( (array) $_wp_registered_nav_menus, $locations );
94
}
95
96
/**
97
 * Unregisters a navigation menu location for a theme.
98
 *
99
 * @global array $_wp_registered_nav_menus
100
 *
101
 * @param string $location The menu location identifier.
102
 * @return bool True on success, false on failure.
103
 */
104
function unregister_nav_menu( $location ) {
105
	global $_wp_registered_nav_menus;
106
107
	if ( is_array( $_wp_registered_nav_menus ) && isset( $_wp_registered_nav_menus[$location] ) ) {
108
		unset( $_wp_registered_nav_menus[$location] );
109
		if ( empty( $_wp_registered_nav_menus ) ) {
110
			_remove_theme_support( 'menus' );
111
		}
112
		return true;
113
	}
114
	return false;
115
}
116
117
/**
118
 * Registers a navigation menu location for a theme.
119
 *
120
 * @since 3.0.0
121
 *
122
 * @param string $location    Menu location identifier, like a slug.
123
 * @param string $description Menu location descriptive text.
124
 */
125
function register_nav_menu( $location, $description ) {
126
	register_nav_menus( array( $location => $description ) );
127
}
128
/**
129
 * Retrieves all registered navigation menu locations in a theme.
130
 *
131
 * @since 3.0.0
132
 *
133
 * @global array $_wp_registered_nav_menus
134
 *
135
 * @return array Registered navigation menu locations. If none are registered, an empty array.
136
 */
137
function get_registered_nav_menus() {
138
	global $_wp_registered_nav_menus;
139
	if ( isset( $_wp_registered_nav_menus ) )
140
		return $_wp_registered_nav_menus;
141
	return array();
142
}
143
144
/**
145
 * Retrieves all registered navigation menu locations and the menus assigned to them.
146
 *
147
 * @since 3.0.0
148
 *
149
 * @return array Registered navigation menu locations and the menus assigned them.
150
 *               If none are registered, an empty array.
151
 */
152
153
function get_nav_menu_locations() {
154
	$locations = get_theme_mod( 'nav_menu_locations' );
155
	return ( is_array( $locations ) ) ? $locations : array();
156
}
157
158
/**
159
 * Determines whether a registered nav menu location has a menu assigned to it.
160
 *
161
 * @since 3.0.0
162
 *
163
 * @param string $location Menu location identifier.
164
 * @return bool Whether location has a menu.
165
 */
166
function has_nav_menu( $location ) {
167
	$has_nav_menu = false;
168
169
	$registered_nav_menus = get_registered_nav_menus();
170
	if ( isset( $registered_nav_menus[ $location ] ) ) {
171
		$locations = get_nav_menu_locations();
172
		$has_nav_menu = ! empty( $locations[ $location ] );
173
	}
174
175
	/**
176
	 * Filters whether a nav menu is assigned to the specified location.
177
	 *
178
	 * @since 4.3.0
179
	 *
180
	 * @param bool   $has_nav_menu Whether there is a menu assigned to a location.
181
	 * @param string $location     Menu location.
182
	 */
183
	return apply_filters( 'has_nav_menu', $has_nav_menu, $location );
184
}
185
186
/**
187
 * Determines whether the given ID is a nav menu item.
188
 *
189
 * @since 3.0.0
190
 *
191
 * @param int $menu_item_id The ID of the potential nav menu item.
192
 * @return bool Whether the given ID is that of a nav menu item.
193
 */
194
function is_nav_menu_item( $menu_item_id = 0 ) {
195
	return ( ! is_wp_error( $menu_item_id ) && ( 'nav_menu_item' == get_post_type( $menu_item_id ) ) );
196
}
197
198
/**
199
 * Creates a navigation menu.
200
 *
201
 * Note that `$menu_name` is expected to be pre-slashed.
202
 *
203
 * @since 3.0.0
204
 *
205
 * @param string $menu_name Menu name.
206
 * @return int|WP_Error Menu ID on success, WP_Error object on failure.
207
 */
208
function wp_create_nav_menu( $menu_name ) {
209
	// expected_slashed ($menu_name)
210
	return wp_update_nav_menu_object( 0, array( 'menu-name' => $menu_name ) );
211
}
212
213
/**
214
 * Delete a Navigation Menu.
215
 *
216
 * @since 3.0.0
217
 *
218
 * @param string $menu Menu ID, slug, or name.
219
 * @return bool|WP_Error True on success, false or WP_Error object on failure.
220
 */
221
function wp_delete_nav_menu( $menu ) {
222
	$menu = wp_get_nav_menu_object( $menu );
223
	if ( ! $menu )
224
		return false;
225
226
	$menu_objects = get_objects_in_term( $menu->term_id, 'nav_menu' );
227
	if ( ! empty( $menu_objects ) ) {
228
		foreach ( $menu_objects as $item ) {
0 ignored issues
show
The expression $menu_objects of type object<WP_Error>|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
229
			wp_delete_post( $item );
230
		}
231
	}
232
233
	$result = wp_delete_term( $menu->term_id, 'nav_menu' );
234
235
	// Remove this menu from any locations.
236
	$locations = get_nav_menu_locations();
237
	foreach ( $locations as $location => $menu_id ) {
238
		if ( $menu_id == $menu->term_id )
239
			$locations[ $location ] = 0;
240
	}
241
	set_theme_mod( 'nav_menu_locations', $locations );
242
243
	if ( $result && !is_wp_error($result) )
244
245
		/**
246
		 * Fires after a navigation menu has been successfully deleted.
247
		 *
248
		 * @since 3.0.0
249
		 *
250
		 * @param int $term_id ID of the deleted menu.
251
		 */
252
		do_action( 'wp_delete_nav_menu', $menu->term_id );
253
254
	return $result;
255
}
256
257
/**
258
 * Save the properties of a menu or create a new menu with those properties.
259
 *
260
 * Note that `$menu_data` is expected to be pre-slashed.
261
 *
262
 * @since 3.0.0
263
 *
264
 * @param int   $menu_id   The ID of the menu or "0" to create a new menu.
265
 * @param array $menu_data The array of menu data.
266
 * @return int|WP_Error Menu ID on success, WP_Error object on failure.
267
 */
268
function wp_update_nav_menu_object( $menu_id = 0, $menu_data = array() ) {
269
	// expected_slashed ($menu_data)
270
	$menu_id = (int) $menu_id;
271
272
	$_menu = wp_get_nav_menu_object( $menu_id );
273
274
	$args = array(
275
		'description' => ( isset( $menu_data['description'] ) ? $menu_data['description']  : '' ),
276
		'name'        => ( isset( $menu_data['menu-name']   ) ? $menu_data['menu-name']    : '' ),
277
		'parent'      => ( isset( $menu_data['parent']      ) ? (int) $menu_data['parent'] : 0  ),
278
		'slug'        => null,
279
	);
280
281
	// double-check that we're not going to have one menu take the name of another
282
	$_possible_existing = get_term_by( 'name', $menu_data['menu-name'], 'nav_menu' );
283
	if (
284
		$_possible_existing &&
285
		! is_wp_error( $_possible_existing ) &&
286
		isset( $_possible_existing->term_id ) &&
287
		$_possible_existing->term_id != $menu_id
288
	) {
289
		return new WP_Error( 'menu_exists',
290
			/* translators: %s: menu name */
291
			sprintf( __( 'The menu name %s conflicts with another menu name. Please try another.' ),
292
				'<strong>' . esc_html( $menu_data['menu-name'] ) . '</strong>'
293
			)
294
		);
295
	}
296
297
	// menu doesn't already exist, so create a new menu
298
	if ( ! $_menu || is_wp_error( $_menu ) ) {
299
		$menu_exists = get_term_by( 'name', $menu_data['menu-name'], 'nav_menu' );
300
301
		if ( $menu_exists ) {
302
			return new WP_Error( 'menu_exists',
303
				/* translators: %s: menu name */
304
				sprintf( __( 'The menu name %s conflicts with another menu name. Please try another.' ),
305
					'<strong>' . esc_html( $menu_data['menu-name'] ) . '</strong>'
306
				)
307
			);
308
		}
309
310
		$_menu = wp_insert_term( $menu_data['menu-name'], 'nav_menu', $args );
311
312
		if ( is_wp_error( $_menu ) )
313
			return $_menu;
314
315
		/**
316
		 * Fires after a navigation menu is successfully created.
317
		 *
318
		 * @since 3.0.0
319
		 *
320
		 * @param int   $term_id   ID of the new menu.
321
		 * @param array $menu_data An array of menu data.
322
		 */
323
		do_action( 'wp_create_nav_menu', $_menu['term_id'], $menu_data );
324
325
		return (int) $_menu['term_id'];
326
	}
327
328
	if ( ! $_menu || ! isset( $_menu->term_id ) )
329
		return 0;
330
331
	$menu_id = (int) $_menu->term_id;
332
333
	$update_response = wp_update_term( $menu_id, 'nav_menu', $args );
334
335
	if ( is_wp_error( $update_response ) )
336
		return $update_response;
337
338
	$menu_id = (int) $update_response['term_id'];
339
340
	/**
341
	 * Fires after a navigation menu has been successfully updated.
342
	 *
343
	 * @since 3.0.0
344
	 *
345
	 * @param int   $menu_id   ID of the updated menu.
346
	 * @param array $menu_data An array of menu data.
347
	 */
348
	do_action( 'wp_update_nav_menu', $menu_id, $menu_data );
349
	return $menu_id;
350
}
351
352
/**
353
 * Save the properties of a menu item or create a new one.
354
 *
355
 * The menu-item-title, menu-item-description, and menu-item-attr-title are expected
356
 * to be pre-slashed since they are passed directly into `wp_insert_post()`.
357
 *
358
 * @since 3.0.0
359
 *
360
 * @param int   $menu_id         The ID of the menu. Required. If "0", makes the menu item a draft orphan.
361
 * @param int   $menu_item_db_id The ID of the menu item. If "0", creates a new menu item.
362
 * @param array $menu_item_data  The menu item's data.
363
 * @return int|WP_Error The menu item's database ID or WP_Error object on failure.
364
 */
365
function wp_update_nav_menu_item( $menu_id = 0, $menu_item_db_id = 0, $menu_item_data = array() ) {
366
	$menu_id = (int) $menu_id;
367
	$menu_item_db_id = (int) $menu_item_db_id;
368
369
	// make sure that we don't convert non-nav_menu_item objects into nav_menu_item objects
370
	if ( ! empty( $menu_item_db_id ) && ! is_nav_menu_item( $menu_item_db_id ) )
371
		return new WP_Error( 'update_nav_menu_item_failed', __( 'The given object ID is not that of a menu item.' ) );
372
373
	$menu = wp_get_nav_menu_object( $menu_id );
374
375
	if ( ! $menu && 0 !== $menu_id ) {
376
		return new WP_Error( 'invalid_menu_id', __( 'Invalid menu ID.' ) );
377
	}
378
379
	if ( is_wp_error( $menu ) ) {
380
		return $menu;
381
	}
382
383
	$defaults = array(
384
		'menu-item-db-id' => $menu_item_db_id,
385
		'menu-item-object-id' => 0,
386
		'menu-item-object' => '',
387
		'menu-item-parent-id' => 0,
388
		'menu-item-position' => 0,
389
		'menu-item-type' => 'custom',
390
		'menu-item-title' => '',
391
		'menu-item-url' => '',
392
		'menu-item-description' => '',
393
		'menu-item-attr-title' => '',
394
		'menu-item-target' => '',
395
		'menu-item-classes' => '',
396
		'menu-item-xfn' => '',
397
		'menu-item-status' => '',
398
	);
399
400
	$args = wp_parse_args( $menu_item_data, $defaults );
401
402
	if ( 0 == $menu_id ) {
403
		$args['menu-item-position'] = 1;
404
	} elseif ( 0 == (int) $args['menu-item-position'] ) {
405
		$menu_items = 0 == $menu_id ? array() : (array) wp_get_nav_menu_items( $menu_id, array( 'post_status' => 'publish,draft' ) );
406
		$last_item = array_pop( $menu_items );
407
		$args['menu-item-position'] = ( $last_item && isset( $last_item->menu_order ) ) ? 1 + $last_item->menu_order : count( $menu_items );
408
	}
409
410
	$original_parent = 0 < $menu_item_db_id ? get_post_field( 'post_parent', $menu_item_db_id ) : 0;
411
412
	if ( 'custom' != $args['menu-item-type'] ) {
413
		/* if non-custom menu item, then:
414
			* use original object's URL
415
			* blank default title to sync with original object's
416
		*/
417
418
		$args['menu-item-url'] = '';
419
420
		$original_title = '';
421
		if ( 'taxonomy' == $args['menu-item-type'] ) {
422
			$original_parent = get_term_field( 'parent', $args['menu-item-object-id'], $args['menu-item-object'], 'raw' );
423
			$original_title = get_term_field( 'name', $args['menu-item-object-id'], $args['menu-item-object'], 'raw' );
424
		} elseif ( 'post_type' == $args['menu-item-type'] ) {
425
426
			$original_object = get_post( $args['menu-item-object-id'] );
427
			$original_parent = (int) $original_object->post_parent;
428
			$original_title = $original_object->post_title;
429
		} elseif ( 'post_type_archive' == $args['menu-item-type'] ) {
430
			$original_object = get_post_type_object( $args['menu-item-object'] );
431
			if ( $original_object ) {
432
				$original_title = $original_object->labels->archives;
433
			}
434
		}
435
436
		if ( $args['menu-item-title'] == $original_title )
437
			$args['menu-item-title'] = '';
438
439
		// hack to get wp to create a post object when too many properties are empty
440
		if ( '' ==  $args['menu-item-title'] && '' == $args['menu-item-description'] )
441
			$args['menu-item-description'] = ' ';
442
	}
443
444
	// Populate the menu item object
445
	$post = array(
446
		'menu_order' => $args['menu-item-position'],
447
		'ping_status' => 0,
448
		'post_content' => $args['menu-item-description'],
449
		'post_excerpt' => $args['menu-item-attr-title'],
450
		'post_parent' => $original_parent,
451
		'post_title' => $args['menu-item-title'],
452
		'post_type' => 'nav_menu_item',
453
	);
454
455
	$update = 0 != $menu_item_db_id;
456
457
	// New menu item. Default is draft status
458
	if ( ! $update ) {
459
		$post['ID'] = 0;
460
		$post['post_status'] = 'publish' == $args['menu-item-status'] ? 'publish' : 'draft';
461
		$menu_item_db_id = wp_insert_post( $post );
462
		if ( ! $menu_item_db_id	|| is_wp_error( $menu_item_db_id ) )
463
			return $menu_item_db_id;
464
465
		/**
466
		 * Fires immediately after a new navigation menu item has been added.
467
		 *
468
		 * @since 4.4.0
469
		 *
470
		 * @see wp_update_nav_menu_item()
471
		 *
472
		 * @param int   $menu_id         ID of the updated menu.
473
		 * @param int   $menu_item_db_id ID of the new menu item.
474
		 * @param array $args            An array of arguments used to update/add the menu item.
475
		 */
476
		do_action( 'wp_add_nav_menu_item', $menu_id, $menu_item_db_id, $args );
477
	}
478
479
	// Associate the menu item with the menu term
480
	// Only set the menu term if it isn't set to avoid unnecessary wp_get_object_terms()
481
	 if ( $menu_id && ( ! $update || ! is_object_in_term( $menu_item_db_id, 'nav_menu', (int) $menu->term_id ) ) ) {
0 ignored issues
show
It seems like $menu_item_db_id defined by wp_insert_post($post) on line 461 can also be of type object<WP_Error>; however, is_object_in_term() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
482
		wp_set_object_terms( $menu_item_db_id, array( $menu->term_id ), 'nav_menu' );
0 ignored issues
show
It seems like $menu_item_db_id defined by wp_insert_post($post) on line 461 can also be of type object<WP_Error>; however, wp_set_object_terms() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
483
	}
484
485
	if ( 'custom' == $args['menu-item-type'] ) {
486
		$args['menu-item-object-id'] = $menu_item_db_id;
487
		$args['menu-item-object'] = 'custom';
488
	}
489
490
	$menu_item_db_id = (int) $menu_item_db_id;
491
492
	update_post_meta( $menu_item_db_id, '_menu_item_type', sanitize_key($args['menu-item-type']) );
493
	update_post_meta( $menu_item_db_id, '_menu_item_menu_item_parent', strval( (int) $args['menu-item-parent-id'] ) );
494
	update_post_meta( $menu_item_db_id, '_menu_item_object_id', strval( (int) $args['menu-item-object-id'] ) );
495
	update_post_meta( $menu_item_db_id, '_menu_item_object', sanitize_key($args['menu-item-object']) );
496
	update_post_meta( $menu_item_db_id, '_menu_item_target', sanitize_key($args['menu-item-target']) );
497
498
	$args['menu-item-classes'] = array_map( 'sanitize_html_class', explode( ' ', $args['menu-item-classes'] ) );
499
	$args['menu-item-xfn'] = implode( ' ', array_map( 'sanitize_html_class', explode( ' ', $args['menu-item-xfn'] ) ) );
500
	update_post_meta( $menu_item_db_id, '_menu_item_classes', $args['menu-item-classes'] );
501
	update_post_meta( $menu_item_db_id, '_menu_item_xfn', $args['menu-item-xfn'] );
502
	update_post_meta( $menu_item_db_id, '_menu_item_url', esc_url_raw($args['menu-item-url']) );
503
504
	if ( 0 == $menu_id )
505
		update_post_meta( $menu_item_db_id, '_menu_item_orphaned', (string) time() );
506
	elseif ( get_post_meta( $menu_item_db_id, '_menu_item_orphaned' ) )
507
		delete_post_meta( $menu_item_db_id, '_menu_item_orphaned' );
508
509
	// Update existing menu item. Default is publish status
510
	if ( $update ) {
511
		$post['ID'] = $menu_item_db_id;
512
		$post['post_status'] = 'draft' == $args['menu-item-status'] ? 'draft' : 'publish';
513
		wp_update_post( $post );
514
	}
515
516
	/**
517
	 * Fires after a navigation menu item has been updated.
518
	 *
519
	 * @since 3.0.0
520
	 *
521
	 * @see wp_update_nav_menu_item()
522
	 *
523
	 * @param int   $menu_id         ID of the updated menu.
524
	 * @param int   $menu_item_db_id ID of the updated menu item.
525
	 * @param array $args            An array of arguments used to update a menu item.
526
	 */
527
	do_action( 'wp_update_nav_menu_item', $menu_id, $menu_item_db_id, $args );
528
529
	return $menu_item_db_id;
530
}
531
532
/**
533
 * Returns all navigation menu objects.
534
 *
535
 * @since 3.0.0
536
 * @since 4.1.0 Default value of the 'orderby' argument was changed from 'none'
537
 *              to 'name'.
538
 *
539
 * @param array $args Optional. Array of arguments passed on to get_terms().
540
 *                    Default empty array.
541
 * @return array Menu objects.
542
 */
543
function wp_get_nav_menus( $args = array() ) {
544
	$defaults = array( 'hide_empty' => false, 'orderby' => 'name' );
545
	$args = wp_parse_args( $args, $defaults );
546
547
	/**
548
	 * Filters the navigation menu objects being returned.
549
	 *
550
	 * @since 3.0.0
551
	 *
552
	 * @see get_terms()
553
	 *
554
	 * @param array $menus An array of menu objects.
555
	 * @param array $args  An array of arguments used to retrieve menu objects.
556
	 */
557
	return apply_filters( 'wp_get_nav_menus', get_terms( 'nav_menu',  $args), $args );
558
}
559
560
/**
561
 * Sort menu items by the desired key.
562
 *
563
 * @since 3.0.0
564
 * @access private
565
 *
566
 * @global string $_menu_item_sort_prop
567
 *
568
 * @param object $a The first object to compare
569
 * @param object $b The second object to compare
570
 * @return int -1, 0, or 1 if $a is considered to be respectively less than, equal to, or greater than $b.
571
 */
572
function _sort_nav_menu_items( $a, $b ) {
573
	global $_menu_item_sort_prop;
574
575
	if ( empty( $_menu_item_sort_prop ) )
576
		return 0;
577
578
	if ( ! isset( $a->$_menu_item_sort_prop ) || ! isset( $b->$_menu_item_sort_prop ) )
579
		return 0;
580
581
	$_a = (int) $a->$_menu_item_sort_prop;
582
	$_b = (int) $b->$_menu_item_sort_prop;
583
584
	if ( $a->$_menu_item_sort_prop == $b->$_menu_item_sort_prop )
585
		return 0;
586
	elseif ( $_a == $a->$_menu_item_sort_prop && $_b == $b->$_menu_item_sort_prop )
587
		return $_a < $_b ? -1 : 1;
588
	else
589
		return strcmp( $a->$_menu_item_sort_prop, $b->$_menu_item_sort_prop );
590
}
591
592
/**
593
 * Return if a menu item is valid.
594
 *
595
 * @link https://core.trac.wordpress.org/ticket/13958
596
 *
597
 * @since 3.2.0
598
 * @access private
599
 *
600
 * @param object $item The menu item to check.
601
 * @return bool False if invalid, otherwise true.
602
 */
603
function _is_valid_nav_menu_item( $item ) {
604
	return empty( $item->_invalid );
605
}
606
607
/**
608
 * Return all menu items of a navigation menu.
609
 *
610
 * @since 3.0.0
611
 *
612
 * @global string $_menu_item_sort_prop
613
 * @staticvar array $fetched
614
 *
615
 * @param string $menu Menu name, ID, or slug.
616
 * @param array  $args Optional. Arguments to pass to get_posts().
617
 * @return false|array $items Array of menu items, otherwise false.
618
 */
619
function wp_get_nav_menu_items( $menu, $args = array() ) {
620
	$menu = wp_get_nav_menu_object( $menu );
621
622
	if ( ! $menu ) {
623
		return false;
624
	}
625
626
	static $fetched = array();
627
628
	$items = get_objects_in_term( $menu->term_id, 'nav_menu' );
629
	if ( is_wp_error( $items ) ) {
630
		return false;
631
	}
632
633
	$defaults = array( 'order' => 'ASC', 'orderby' => 'menu_order', 'post_type' => 'nav_menu_item',
634
		'post_status' => 'publish', 'output' => ARRAY_A, 'output_key' => 'menu_order', 'nopaging' => true );
635
	$args = wp_parse_args( $args, $defaults );
636
	$args['include'] = $items;
637
638
	if ( ! empty( $items ) ) {
639
		$items = get_posts( $args );
640
	} else {
641
		$items = array();
642
	}
643
644
	// Get all posts and terms at once to prime the caches
645
	if ( empty( $fetched[$menu->term_id] ) || wp_using_ext_object_cache() ) {
646
		$fetched[$menu->term_id] = true;
647
		$posts = array();
648
		$terms = array();
649
		foreach ( $items as $item ) {
650
			$object_id = get_post_meta( $item->ID, '_menu_item_object_id', true );
651
			$object    = get_post_meta( $item->ID, '_menu_item_object',    true );
652
			$type      = get_post_meta( $item->ID, '_menu_item_type',      true );
653
654
			if ( 'post_type' == $type )
655
				$posts[$object][] = $object_id;
656
			elseif ( 'taxonomy' == $type)
657
				$terms[$object][] = $object_id;
658
		}
659
660
		if ( ! empty( $posts ) ) {
661
			foreach ( array_keys($posts) as $post_type ) {
662
				get_posts( array('post__in' => $posts[$post_type], 'post_type' => $post_type, 'nopaging' => true, 'update_post_term_cache' => false) );
663
			}
664
		}
665
		unset($posts);
666
667
		if ( ! empty( $terms ) ) {
668
			foreach ( array_keys($terms) as $taxonomy ) {
669
				get_terms( $taxonomy, array(
670
					'include' => $terms[ $taxonomy ],
671
					'hierarchical' => false,
672
				) );
673
			}
674
		}
675
		unset($terms);
676
	}
677
678
	$items = array_map( 'wp_setup_nav_menu_item', $items );
679
680
	if ( ! is_admin() ) { // Remove invalid items only in front end
681
		$items = array_filter( $items, '_is_valid_nav_menu_item' );
682
	}
683
684 View Code Duplication
	if ( ARRAY_A == $args['output'] ) {
685
		$GLOBALS['_menu_item_sort_prop'] = $args['output_key'];
686
		usort($items, '_sort_nav_menu_items');
687
		$i = 1;
688
		foreach ( $items as $k => $item ) {
689
			$items[$k]->{$args['output_key']} = $i++;
690
		}
691
	}
692
693
	/**
694
	 * Filters the navigation menu items being returned.
695
	 *
696
	 * @since 3.0.0
697
	 *
698
	 * @param array  $items An array of menu item post objects.
699
	 * @param object $menu  The menu object.
700
	 * @param array  $args  An array of arguments used to retrieve menu item objects.
701
	 */
702
	return apply_filters( 'wp_get_nav_menu_items', $items, $menu, $args );
703
}
704
705
/**
706
 * Decorates a menu item object with the shared navigation menu item properties.
707
 *
708
 * Properties:
709
 * - ID:               The term_id if the menu item represents a taxonomy term.
710
 * - attr_title:       The title attribute of the link element for this menu item.
711
 * - classes:          The array of class attribute values for the link element of this menu item.
712
 * - db_id:            The DB ID of this item as a nav_menu_item object, if it exists (0 if it doesn't exist).
713
 * - description:      The description of this menu item.
714
 * - menu_item_parent: The DB ID of the nav_menu_item that is this item's menu parent, if any. 0 otherwise.
715
 * - object:           The type of object originally represented, such as "category," "post", or "attachment."
716
 * - object_id:        The DB ID of the original object this menu item represents, e.g. ID for posts and term_id for categories.
717
 * - post_parent:      The DB ID of the original object's parent object, if any (0 otherwise).
718
 * - post_title:       A "no title" label if menu item represents a post that lacks a title.
719
 * - target:           The target attribute of the link element for this menu item.
720
 * - title:            The title of this menu item.
721
 * - type:             The family of objects originally represented, such as "post_type" or "taxonomy."
722
 * - type_label:       The singular label used to describe this type of menu item.
723
 * - url:              The URL to which this menu item points.
724
 * - xfn:              The XFN relationship expressed in the link of this menu item.
725
 * - _invalid:         Whether the menu item represents an object that no longer exists.
726
 *
727
 * @since 3.0.0
728
 *
729
 * @param object $menu_item The menu item to modify.
730
 * @return object $menu_item The menu item with standard menu item properties.
731
 */
732
function wp_setup_nav_menu_item( $menu_item ) {
733
	if ( isset( $menu_item->post_type ) ) {
734
		if ( 'nav_menu_item' == $menu_item->post_type ) {
735
			$menu_item->db_id = (int) $menu_item->ID;
736
			$menu_item->menu_item_parent = ! isset( $menu_item->menu_item_parent ) ? get_post_meta( $menu_item->ID, '_menu_item_menu_item_parent', true ) : $menu_item->menu_item_parent;
737
			$menu_item->object_id = ! isset( $menu_item->object_id ) ? get_post_meta( $menu_item->ID, '_menu_item_object_id', true ) : $menu_item->object_id;
738
			$menu_item->object = ! isset( $menu_item->object ) ? get_post_meta( $menu_item->ID, '_menu_item_object', true ) : $menu_item->object;
739
			$menu_item->type = ! isset( $menu_item->type ) ? get_post_meta( $menu_item->ID, '_menu_item_type', true ) : $menu_item->type;
740
741
			if ( 'post_type' == $menu_item->type ) {
742
				$object = get_post_type_object( $menu_item->object );
743 View Code Duplication
				if ( $object ) {
744
					$menu_item->type_label = $object->labels->singular_name;
745
				} else {
746
					$menu_item->type_label = $menu_item->object;
747
					$menu_item->_invalid = true;
748
				}
749
750
				$menu_item->url = get_permalink( $menu_item->object_id );
751
752
				$original_object = get_post( $menu_item->object_id );
753
				/** This filter is documented in wp-includes/post-template.php */
754
				$original_title = apply_filters( 'the_title', $original_object->post_title, $original_object->ID );
755
756
				if ( '' === $original_title ) {
757
					/* translators: %d: ID of a post */
758
					$original_title = sprintf( __( '#%d (no title)' ), $original_object->ID );
759
				}
760
761
				$menu_item->title = '' == $menu_item->post_title ? $original_title : $menu_item->post_title;
762
763
			} elseif ( 'post_type_archive' == $menu_item->type ) {
764
				$object =  get_post_type_object( $menu_item->object );
765
				if ( $object ) {
766
					$menu_item->title = '' == $menu_item->post_title ? $object->labels->archives : $menu_item->post_title;
767
					$post_type_description = $object->description;
768
				} else {
769
					$menu_item->_invalid = true;
770
					$post_type_description = '';
771
				}
772
773
				$menu_item->type_label = __( 'Post Type Archive' );
774
				$post_content = wp_trim_words( $menu_item->post_content, 200 );
775
				$post_type_description = '' == $post_content ? $post_type_description : $post_content; 
0 ignored issues
show
$post_type_description 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...
776
				$menu_item->url = get_post_type_archive_link( $menu_item->object );
777
			} elseif ( 'taxonomy' == $menu_item->type ) {
778
				$object = get_taxonomy( $menu_item->object );
779 View Code Duplication
				if ( $object ) {
780
					$menu_item->type_label = $object->labels->singular_name;
781
				} else {
782
					$menu_item->type_label = $menu_item->object;
783
					$menu_item->_invalid = true;
784
				}
785
786
				$term_url = get_term_link( (int) $menu_item->object_id, $menu_item->object );
787
				$menu_item->url = !is_wp_error( $term_url ) ? $term_url : '';
788
789
				$original_title = get_term_field( 'name', $menu_item->object_id, $menu_item->object, 'raw' );
790
				if ( is_wp_error( $original_title ) )
791
					$original_title = false;
792
				$menu_item->title = '' == $menu_item->post_title ? $original_title : $menu_item->post_title;
793
794
			} else {
795
				$menu_item->type_label = __('Custom Link');
796
				$menu_item->title = $menu_item->post_title;
797
				$menu_item->url = ! isset( $menu_item->url ) ? get_post_meta( $menu_item->ID, '_menu_item_url', true ) : $menu_item->url;
798
			}
799
800
			$menu_item->target = ! isset( $menu_item->target ) ? get_post_meta( $menu_item->ID, '_menu_item_target', true ) : $menu_item->target;
801
802
			/**
803
			 * Filters a navigation menu item's title attribute.
804
			 *
805
			 * @since 3.0.0
806
			 *
807
			 * @param string $item_title The menu item title attribute.
808
			 */
809
			$menu_item->attr_title = ! isset( $menu_item->attr_title ) ? apply_filters( 'nav_menu_attr_title', $menu_item->post_excerpt ) : $menu_item->attr_title;
810
811
			if ( ! isset( $menu_item->description ) ) {
812
				/**
813
				 * Filters a navigation menu item's description.
814
				 *
815
				 * @since 3.0.0
816
				 *
817
				 * @param string $description The menu item description.
818
				 */
819
				$menu_item->description = apply_filters( 'nav_menu_description', wp_trim_words( $menu_item->post_content, 200 ) );
820
			}
821
822
			$menu_item->classes = ! isset( $menu_item->classes ) ? (array) get_post_meta( $menu_item->ID, '_menu_item_classes', true ) : $menu_item->classes;
823
			$menu_item->xfn = ! isset( $menu_item->xfn ) ? get_post_meta( $menu_item->ID, '_menu_item_xfn', true ) : $menu_item->xfn;
824
		} else {
825
			$menu_item->db_id = 0;
826
			$menu_item->menu_item_parent = 0;
827
			$menu_item->object_id = (int) $menu_item->ID;
828
			$menu_item->type = 'post_type';
829
830
			$object = get_post_type_object( $menu_item->post_type );
831
			$menu_item->object = $object->name;
832
			$menu_item->type_label = $object->labels->singular_name;
833
834
			if ( '' === $menu_item->post_title ) {
835
				/* translators: %d: ID of a post */
836
				$menu_item->post_title = sprintf( __( '#%d (no title)' ), $menu_item->ID );
837
			}
838
839
			$menu_item->title = $menu_item->post_title;
840
			$menu_item->url = get_permalink( $menu_item->ID );
841
			$menu_item->target = '';
842
843
			/** This filter is documented in wp-includes/nav-menu.php */
844
			$menu_item->attr_title = apply_filters( 'nav_menu_attr_title', '' );
845
846
			/** This filter is documented in wp-includes/nav-menu.php */
847
			$menu_item->description = apply_filters( 'nav_menu_description', '' );
848
			$menu_item->classes = array();
849
			$menu_item->xfn = '';
850
		}
851
	} elseif ( isset( $menu_item->taxonomy ) ) {
852
		$menu_item->ID = $menu_item->term_id;
853
		$menu_item->db_id = 0;
854
		$menu_item->menu_item_parent = 0;
855
		$menu_item->object_id = (int) $menu_item->term_id;
856
		$menu_item->post_parent = (int) $menu_item->parent;
857
		$menu_item->type = 'taxonomy';
858
859
		$object = get_taxonomy( $menu_item->taxonomy );
860
		$menu_item->object = $object->name;
861
		$menu_item->type_label = $object->labels->singular_name;
862
863
		$menu_item->title = $menu_item->name;
864
		$menu_item->url = get_term_link( $menu_item, $menu_item->taxonomy );
865
		$menu_item->target = '';
866
		$menu_item->attr_title = '';
867
		$menu_item->description = get_term_field( 'description', $menu_item->term_id, $menu_item->taxonomy );
868
		$menu_item->classes = array();
869
		$menu_item->xfn = '';
870
871
	}
872
873
	/**
874
	 * Filters a navigation menu item object.
875
	 *
876
	 * @since 3.0.0
877
	 *
878
	 * @param object $menu_item The menu item object.
879
	 */
880
	return apply_filters( 'wp_setup_nav_menu_item', $menu_item );
881
}
882
883
/**
884
 * Get the menu items associated with a particular object.
885
 *
886
 * @since 3.0.0
887
 *
888
 * @param int    $object_id   The ID of the original object.
889
 * @param string $object_type The type of object, such as "taxonomy" or "post_type."
890
 * @param string $taxonomy    If $object_type is "taxonomy", $taxonomy is the name of the tax that $object_id belongs to
891
 * @return array The array of menu item IDs; empty array if none;
892
 */
893
function wp_get_associated_nav_menu_items( $object_id = 0, $object_type = 'post_type', $taxonomy = '' ) {
894
	$object_id = (int) $object_id;
895
	$menu_item_ids = array();
896
897
	$query = new WP_Query;
898
	$menu_items = $query->query(
899
		array(
0 ignored issues
show
array('meta_key' => '_me...'posts_per_page' => -1) is of type array<string,string|inte...s_per_page":"integer"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
900
			'meta_key' => '_menu_item_object_id',
901
			'meta_value' => $object_id,
902
			'post_status' => 'any',
903
			'post_type' => 'nav_menu_item',
904
			'posts_per_page' => -1,
905
		)
906
	);
907
	foreach ( (array) $menu_items as $menu_item ) {
908
		if ( isset( $menu_item->ID ) && is_nav_menu_item( $menu_item->ID ) ) {
909
			$menu_item_type = get_post_meta( $menu_item->ID, '_menu_item_type', true );
910
			if (
911
				'post_type' == $object_type &&
912
				'post_type' == $menu_item_type
913
			) {
914
				$menu_item_ids[] = (int) $menu_item->ID;
915
			} elseif (
916
				'taxonomy' == $object_type &&
917
				'taxonomy' == $menu_item_type &&
918
				get_post_meta( $menu_item->ID, '_menu_item_object', true ) == $taxonomy
919
			) {
920
				$menu_item_ids[] = (int) $menu_item->ID;
921
			}
922
		}
923
	}
924
925
	return array_unique( $menu_item_ids );
926
}
927
928
/**
929
 * Callback for handling a menu item when its original object is deleted.
930
 *
931
 * @since 3.0.0
932
 * @access private
933
 *
934
 * @param int $object_id The ID of the original object being trashed.
935
 *
936
 */
937 View Code Duplication
function _wp_delete_post_menu_item( $object_id = 0 ) {
0 ignored issues
show
This function seems to be duplicated in 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...
938
	$object_id = (int) $object_id;
939
940
	$menu_item_ids = wp_get_associated_nav_menu_items( $object_id, 'post_type' );
941
942
	foreach ( (array) $menu_item_ids as $menu_item_id ) {
943
		wp_delete_post( $menu_item_id, true );
944
	}
945
}
946
947
/**
948
 * Serves as a callback for handling a menu item when its original object is deleted.
949
 *
950
 * @since 3.0.0
951
 * @access private
952
 *
953
 * @param int    $object_id Optional. The ID of the original object being trashed. Default 0.
954
 * @param int    $tt_id     Term taxonomy ID. Unused.
955
 * @param string $taxonomy  Taxonomy slug.
956
 */
957 View Code Duplication
function _wp_delete_tax_menu_item( $object_id = 0, $tt_id, $taxonomy ) {
0 ignored issues
show
This function seems to be duplicated in 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...
958
	$object_id = (int) $object_id;
959
960
	$menu_item_ids = wp_get_associated_nav_menu_items( $object_id, 'taxonomy', $taxonomy );
961
962
	foreach ( (array) $menu_item_ids as $menu_item_id ) {
963
		wp_delete_post( $menu_item_id, true );
964
	}
965
}
966
967
/**
968
 * Automatically add newly published page objects to menus with that as an option.
969
 *
970
 * @since 3.0.0
971
 * @access private
972
 *
973
 * @param string $new_status The new status of the post object.
974
 * @param string $old_status The old status of the post object.
975
 * @param object $post       The post object being transitioned from one status to another.
976
 */
977
function _wp_auto_add_pages_to_menu( $new_status, $old_status, $post ) {
978
	if ( 'publish' != $new_status || 'publish' == $old_status || 'page' != $post->post_type )
979
		return;
980
	if ( ! empty( $post->post_parent ) )
981
		return;
982
	$auto_add = get_option( 'nav_menu_options' );
983
	if ( empty( $auto_add ) || ! is_array( $auto_add ) || ! isset( $auto_add['auto_add'] ) )
984
		return;
985
	$auto_add = $auto_add['auto_add'];
986
	if ( empty( $auto_add ) || ! is_array( $auto_add ) )
987
		return;
988
989
	$args = array(
990
		'menu-item-object-id' => $post->ID,
991
		'menu-item-object' => $post->post_type,
992
		'menu-item-type' => 'post_type',
993
		'menu-item-status' => 'publish',
994
	);
995
996
	foreach ( $auto_add as $menu_id ) {
997
		$items = wp_get_nav_menu_items( $menu_id, array( 'post_status' => 'publish,draft' ) );
998
		if ( ! is_array( $items ) )
999
			continue;
1000
		foreach ( $items as $item ) {
1001
			if ( $post->ID == $item->object_id )
1002
				continue 2;
1003
		}
1004
		wp_update_nav_menu_item( $menu_id, 0, $args );
1005
	}
1006
}
1007