WP_Customize_Nav_Menu_Item_Setting   D
last analyzed

Complexity

Total Complexity 125

Size/Duplication

Total Lines 882
Duplicated Lines 8.62 %

Coupling/Cohesion

Components 2
Dependencies 7

Importance

Changes 0
Metric Value
dl 76
loc 882
rs 4
c 0
b 0
f 0
wmc 125
lcom 2
cbo 7

13 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 23 5
A flush_cached_value() 0 6 2
C value() 0 46 14
D get_original_title() 6 27 10
B get_type_label() 0 22 6
F populate_value() 0 86 20
B preview() 0 28 5
C filter_wp_get_nav_menu_items() 0 60 15
B sort_wp_get_nav_menu_items() 10 22 4
D value_as_wp_post_nav_menu_item() 0 56 15
C sanitize() 0 67 8
D update() 60 121 18
A amend_customize_save_response() 0 13 3

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like WP_Customize_Nav_Menu_Item_Setting 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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 WP_Customize_Nav_Menu_Item_Setting, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Customize API: WP_Customize_Nav_Menu_Item_Setting class
4
 *
5
 * @package WordPress
6
 * @subpackage Customize
7
 * @since 4.4.0
8
 */
9
10
/**
11
 * Customize Setting to represent a nav_menu.
12
 *
13
 * Subclass of WP_Customize_Setting to represent a nav_menu taxonomy term, and
14
 * the IDs for the nav_menu_items associated with the nav menu.
15
 *
16
 * @since 4.3.0
17
 *
18
 * @see WP_Customize_Setting
19
 */
20
class WP_Customize_Nav_Menu_Item_Setting extends WP_Customize_Setting {
21
22
	const ID_PATTERN = '/^nav_menu_item\[(?P<id>-?\d+)\]$/';
23
24
	const POST_TYPE = 'nav_menu_item';
25
26
	const TYPE = 'nav_menu_item';
27
28
	/**
29
	 * Setting type.
30
	 *
31
	 * @since 4.3.0
32
	 * @access public
33
	 * @var string
34
	 */
35
	public $type = self::TYPE;
36
37
	/**
38
	 * Default setting value.
39
	 *
40
	 * @since 4.3.0
41
	 * @access public
42
	 * @var array
43
	 *
44
	 * @see wp_setup_nav_menu_item()
45
	 */
46
	public $default = array(
47
		// The $menu_item_data for wp_update_nav_menu_item().
48
		'object_id'        => 0,
49
		'object'           => '', // Taxonomy name.
50
		'menu_item_parent' => 0, // A.K.A. menu-item-parent-id; note that post_parent is different, and not included.
51
		'position'         => 0, // A.K.A. menu_order.
52
		'type'             => 'custom', // Note that type_label is not included here.
53
		'title'            => '',
54
		'url'              => '',
55
		'target'           => '',
56
		'attr_title'       => '',
57
		'description'      => '',
58
		'classes'          => '',
59
		'xfn'              => '',
60
		'status'           => 'publish',
61
		'original_title'   => '',
62
		'nav_menu_term_id' => 0, // This will be supplied as the $menu_id arg for wp_update_nav_menu_item().
63
		'_invalid'         => false,
64
	);
65
66
	/**
67
	 * Default transport.
68
	 *
69
	 * @since 4.3.0
70
	 * @since 4.5.0 Default changed to 'refresh'
71
	 * @access public
72
	 * @var string
73
	 */
74
	public $transport = 'refresh';
75
76
	/**
77
	 * The post ID represented by this setting instance. This is the db_id.
78
	 *
79
	 * A negative value represents a placeholder ID for a new menu not yet saved.
80
	 *
81
	 * @since 4.3.0
82
	 * @access public
83
	 * @var int
84
	 */
85
	public $post_id;
86
87
	/**
88
	 * Storage of pre-setup menu item to prevent wasted calls to wp_setup_nav_menu_item().
89
	 *
90
	 * @since 4.3.0
91
	 * @access protected
92
	 * @var array
93
	 */
94
	protected $value;
95
96
	/**
97
	 * Previous (placeholder) post ID used before creating a new menu item.
98
	 *
99
	 * This value will be exported to JS via the customize_save_response filter
100
	 * so that JavaScript can update the settings to refer to the newly-assigned
101
	 * post ID. This value is always negative to indicate it does not refer to
102
	 * a real post.
103
	 *
104
	 * @since 4.3.0
105
	 * @access public
106
	 * @var int
107
	 *
108
	 * @see WP_Customize_Nav_Menu_Item_Setting::update()
109
	 * @see WP_Customize_Nav_Menu_Item_Setting::amend_customize_save_response()
110
	 */
111
	public $previous_post_id;
112
113
	/**
114
	 * When previewing or updating a menu item, this stores the previous nav_menu_term_id
115
	 * which ensures that we can apply the proper filters.
116
	 *
117
	 * @since 4.3.0
118
	 * @access public
119
	 * @var int
120
	 */
121
	public $original_nav_menu_term_id;
122
123
	/**
124
	 * Whether or not update() was called.
125
	 *
126
	 * @since 4.3.0
127
	 * @access protected
128
	 * @var bool
129
	 */
130
	protected $is_updated = false;
131
132
	/**
133
	 * Status for calling the update method, used in customize_save_response filter.
134
	 *
135
	 * See {@see 'customize_save_response'}.
136
	 *
137
	 * When status is inserted, the placeholder post ID is stored in $previous_post_id.
138
	 * When status is error, the error is stored in $update_error.
139
	 *
140
	 * @since 4.3.0
141
	 * @access public
142
	 * @var string updated|inserted|deleted|error
143
	 *
144
	 * @see WP_Customize_Nav_Menu_Item_Setting::update()
145
	 * @see WP_Customize_Nav_Menu_Item_Setting::amend_customize_save_response()
146
	 */
147
	public $update_status;
148
149
	/**
150
	 * Any error object returned by wp_update_nav_menu_item() when setting is updated.
151
	 *
152
	 * @since 4.3.0
153
	 * @access public
154
	 * @var WP_Error
155
	 *
156
	 * @see WP_Customize_Nav_Menu_Item_Setting::update()
157
	 * @see WP_Customize_Nav_Menu_Item_Setting::amend_customize_save_response()
158
	 */
159
	public $update_error;
160
161
	/**
162
	 * Constructor.
163
	 *
164
	 * Any supplied $args override class property defaults.
165
	 *
166
	 * @since 4.3.0
167
	 * @access public
168
	 *
169
	 * @param WP_Customize_Manager $manager Bootstrap Customizer instance.
170
	 * @param string               $id      An specific ID of the setting. Can be a
171
	 *                                      theme mod or option name.
172
	 * @param array                $args    Optional. Setting arguments.
173
	 *
174
	 * @throws Exception If $id is not valid for this setting type.
175
	 */
176
	public function __construct( WP_Customize_Manager $manager, $id, array $args = array() ) {
177
		if ( empty( $manager->nav_menus ) ) {
178
			throw new Exception( 'Expected WP_Customize_Manager::$nav_menus to be set.' );
179
		}
180
181
		if ( ! preg_match( self::ID_PATTERN, $id, $matches ) ) {
182
			throw new Exception( "Illegal widget setting ID: $id" );
183
		}
184
185
		$this->post_id = intval( $matches['id'] );
186
		add_action( 'wp_update_nav_menu_item', array( $this, 'flush_cached_value' ), 10, 2 );
187
188
		parent::__construct( $manager, $id, $args );
189
190
		// Ensure that an initially-supplied value is valid.
191
		if ( isset( $this->value ) ) {
192
			$this->populate_value();
193
			foreach ( array_diff( array_keys( $this->default ), array_keys( $this->value ) ) as $missing ) {
194
				throw new Exception( "Supplied nav_menu_item value missing property: $missing" );
195
			}
196
		}
197
198
	}
199
200
	/**
201
	 * Clear the cached value when this nav menu item is updated.
202
	 *
203
	 * @since 4.3.0
204
	 * @access public
205
	 *
206
	 * @param int $menu_id       The term ID for the menu.
207
	 * @param int $menu_item_id  The post ID for the menu item.
208
	 */
209
	public function flush_cached_value( $menu_id, $menu_item_id ) {
210
		unset( $menu_id );
211
		if ( $menu_item_id === $this->post_id ) {
212
			$this->value = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $value.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
213
		}
214
	}
215
216
	/**
217
	 * Get the instance data for a given nav_menu_item setting.
218
	 *
219
	 * @since 4.3.0
220
	 * @access public
221
	 *
222
	 * @see wp_setup_nav_menu_item()
223
	 *
224
	 * @return array|false Instance data array, or false if the item is marked for deletion.
225
	 */
226
	public function value() {
227
		if ( $this->is_previewed && $this->_previewed_blog_id === get_current_blog_id() ) {
228
			$undefined  = new stdClass(); // Symbol.
229
			$post_value = $this->post_value( $undefined );
230
231
			if ( $undefined === $post_value ) {
232
				$value = $this->_original_value;
233
			} else {
234
				$value = $post_value;
235
			}
236
			if ( ! empty( $value ) && empty( $value['original_title'] ) ) {
237
				$value['original_title'] = $this->get_original_title( (object) $value );
238
			}
239
		} elseif ( isset( $this->value ) ) {
240
			$value = $this->value;
241
		} else {
242
			$value = false;
243
244
			// Note that a ID of less than one indicates a nav_menu not yet inserted.
245
			if ( $this->post_id > 0 ) {
246
				$post = get_post( $this->post_id );
247
				if ( $post && self::POST_TYPE === $post->post_type ) {
248
					$is_title_empty = empty( $post->post_title );
249
					$value = (array) wp_setup_nav_menu_item( $post );
250
					if ( $is_title_empty ) {
251
						$value['title'] = '';
252
					}
253
				}
254
			}
255
256
			if ( ! is_array( $value ) ) {
257
				$value = $this->default;
258
			}
259
260
			// Cache the value for future calls to avoid having to re-call wp_setup_nav_menu_item().
261
			$this->value = $value;
262
			$this->populate_value();
263
			$value = $this->value;
264
		}
265
266
		if ( ! empty( $value ) && empty( $value['type_label'] ) ) {
267
			$value['type_label'] = $this->get_type_label( (object) $value );
268
		}
269
270
		return $value;
271
	}
272
273
	/**
274
	 * Get original title.
275
	 *
276
	 * @since 4.7.0
277
	 * @access protected
278
	 *
279
	 * @param object $item Nav menu item.
280
	 * @return string The original title.
281
	 */
282
	protected function get_original_title( $item ) {
283
		$original_title = '';
284
		if ( 'post_type' === $item->type && ! empty( $item->object_id ) ) {
285
			$original_object = get_post( $item->object_id );
286
			if ( $original_object ) {
287
				/** This filter is documented in wp-includes/post-template.php */
288
				$original_title = apply_filters( 'the_title', $original_object->post_title, $original_object->ID );
289
290
				if ( '' === $original_title ) {
291
					/* translators: %d: ID of a post */
292
					$original_title = sprintf( __( '#%d (no title)' ), $original_object->ID );
293
				}
294
			}
295
		} elseif ( 'taxonomy' === $item->type && ! empty( $item->object_id ) ) {
296
			$original_term_title = get_term_field( 'name', $item->object_id, $item->object, 'raw' );
297
			if ( ! is_wp_error( $original_term_title ) ) {
298
				$original_title = $original_term_title;
299
			}
300 View Code Duplication
		} elseif ( 'post_type_archive' === $item->type ) {
301
			$original_object = get_post_type_object( $item->object );
302
			if ( $original_object ) {
303
				$original_title = $original_object->labels->archives;
304
			}
305
		}
306
		$original_title = html_entity_decode( $original_title, ENT_QUOTES, get_bloginfo( 'charset' ) );
307
		return $original_title;
308
	}
309
310
	/**
311
	 * Get type label.
312
	 *
313
	 * @since 4.7.0
314
	 * @access protected
315
	 *
316
	 * @param object $item Nav menu item.
317
	 * @returns string The type label.
318
	 */
319
	protected function get_type_label( $item ) {
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
320
		if ( 'post_type' === $item->type ) {
321
			$object = get_post_type_object( $item->object );
322
			if ( $object ) {
323
				$type_label = $object->labels->singular_name;
324
			} else {
325
				$type_label = $item->object;
326
			}
327
		} elseif ( 'taxonomy' === $item->type ) {
328
			$object = get_taxonomy( $item->object );
329
			if ( $object ) {
330
				$type_label = $object->labels->singular_name;
331
			} else {
332
				$type_label = $item->object;
333
			}
334
		} elseif ( 'post_type_archive' === $item->type ) {
335
			$type_label = __( 'Post Type Archive' );
336
		} else {
337
			$type_label = __( 'Custom Link' );
338
		}
339
		return $type_label;
340
	}
341
342
	/**
343
	 * Ensure that the value is fully populated with the necessary properties.
344
	 *
345
	 * Translates some properties added by wp_setup_nav_menu_item() and removes others.
346
	 *
347
	 * @since 4.3.0
348
	 * @access protected
349
	 *
350
	 * @see WP_Customize_Nav_Menu_Item_Setting::value()
351
	 */
352
	protected function populate_value() {
353
		if ( ! is_array( $this->value ) ) {
354
			return;
355
		}
356
357
		if ( isset( $this->value['menu_order'] ) ) {
358
			$this->value['position'] = $this->value['menu_order'];
359
			unset( $this->value['menu_order'] );
360
		}
361
		if ( isset( $this->value['post_status'] ) ) {
362
			$this->value['status'] = $this->value['post_status'];
363
			unset( $this->value['post_status'] );
364
		}
365
366
		if ( ! isset( $this->value['original_title'] ) ) {
367
			$this->value['original_title'] = $this->get_original_title( (object) $this->value );
368
		}
369
370
		if ( ! isset( $this->value['nav_menu_term_id'] ) && $this->post_id > 0 ) {
371
			$menus = wp_get_post_terms( $this->post_id, WP_Customize_Nav_Menu_Setting::TAXONOMY, array(
372
				'fields' => 'ids',
373
			) );
374
			if ( ! empty( $menus ) ) {
375
				$this->value['nav_menu_term_id'] = array_shift( $menus );
376
			} else {
377
				$this->value['nav_menu_term_id'] = 0;
378
			}
379
		}
380
381
		foreach ( array( 'object_id', 'menu_item_parent', 'nav_menu_term_id' ) as $key ) {
382
			if ( ! is_int( $this->value[ $key ] ) ) {
383
				$this->value[ $key ] = intval( $this->value[ $key ] );
384
			}
385
		}
386
		foreach ( array( 'classes', 'xfn' ) as $key ) {
387
			if ( is_array( $this->value[ $key ] ) ) {
388
				$this->value[ $key ] = implode( ' ', $this->value[ $key ] );
389
			}
390
		}
391
392
		if ( ! isset( $this->value['title'] ) ) {
393
			$this->value['title'] = '';
394
		}
395
396
		if ( ! isset( $this->value['_invalid'] ) ) {
397
			$this->value['_invalid'] = false;
398
			$is_known_invalid = (
399
				( ( 'post_type' === $this->value['type'] || 'post_type_archive' === $this->value['type'] ) && ! post_type_exists( $this->value['object'] ) )
400
				||
401
				( 'taxonomy' === $this->value['type'] && ! taxonomy_exists( $this->value['object'] ) )
402
			);
403
			if ( $is_known_invalid ) {
404
				$this->value['_invalid'] = true;
405
			}
406
		}
407
408
		// Remove remaining properties available on a setup nav_menu_item post object which aren't relevant to the setting value.
409
		$irrelevant_properties = array(
410
			'ID',
411
			'comment_count',
412
			'comment_status',
413
			'db_id',
414
			'filter',
415
			'guid',
416
			'ping_status',
417
			'pinged',
418
			'post_author',
419
			'post_content',
420
			'post_content_filtered',
421
			'post_date',
422
			'post_date_gmt',
423
			'post_excerpt',
424
			'post_mime_type',
425
			'post_modified',
426
			'post_modified_gmt',
427
			'post_name',
428
			'post_parent',
429
			'post_password',
430
			'post_title',
431
			'post_type',
432
			'to_ping',
433
		);
434
		foreach ( $irrelevant_properties as $property ) {
435
			unset( $this->value[ $property ] );
436
		}
437
	}
438
439
	/**
440
	 * Handle previewing the setting.
441
	 *
442
	 * @since 4.3.0
443
	 * @since 4.4.0 Added boolean return value.
444
	 * @access public
445
	 *
446
	 * @see WP_Customize_Manager::post_value()
447
	 *
448
	 * @return bool False if method short-circuited due to no-op.
449
	 */
450
	public function preview() {
451
		if ( $this->is_previewed ) {
452
			return false;
453
		}
454
455
		$undefined = new stdClass();
456
		$is_placeholder = ( $this->post_id < 0 );
457
		$is_dirty = ( $undefined !== $this->post_value( $undefined ) );
458
		if ( ! $is_placeholder && ! $is_dirty ) {
459
			return false;
460
		}
461
462
		$this->is_previewed              = true;
463
		$this->_original_value           = $this->value();
464
		$this->original_nav_menu_term_id = $this->_original_value['nav_menu_term_id'];
465
		$this->_previewed_blog_id        = get_current_blog_id();
0 ignored issues
show
Documentation Bug introduced by
It seems like get_current_blog_id() can also be of type double. However, the property $_previewed_blog_id is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
466
467
		add_filter( 'wp_get_nav_menu_items', array( $this, 'filter_wp_get_nav_menu_items' ), 10, 3 );
468
469
		$sort_callback = array( __CLASS__, 'sort_wp_get_nav_menu_items' );
470
		if ( ! has_filter( 'wp_get_nav_menu_items', $sort_callback ) ) {
471
			add_filter( 'wp_get_nav_menu_items', array( __CLASS__, 'sort_wp_get_nav_menu_items' ), 1000, 3 );
472
		}
473
474
		// @todo Add get_post_metadata filters for plugins to add their data.
475
476
		return true;
477
	}
478
479
	/**
480
	 * Filters the wp_get_nav_menu_items() result to supply the previewed menu items.
481
	 *
482
	 * @since 4.3.0
483
	 * @access public
484
	 *
485
	 * @see wp_get_nav_menu_items()
486
	 *
487
	 * @param array  $items An array of menu item post objects.
488
	 * @param object $menu  The menu object.
489
	 * @param array  $args  An array of arguments used to retrieve menu item objects.
490
	 * @return array Array of menu items,
491
	 */
492
	public function filter_wp_get_nav_menu_items( $items, $menu, $args ) {
0 ignored issues
show
Unused Code introduced by
The parameter $args is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
493
		$this_item = $this->value();
494
		$current_nav_menu_term_id = $this_item['nav_menu_term_id'];
495
		unset( $this_item['nav_menu_term_id'] );
496
497
		$should_filter = (
498
			$menu->term_id === $this->original_nav_menu_term_id
499
			||
500
			$menu->term_id === $current_nav_menu_term_id
501
		);
502
		if ( ! $should_filter ) {
503
			return $items;
504
		}
505
506
		// Handle deleted menu item, or menu item moved to another menu.
507
		$should_remove = (
508
			false === $this_item
509
			||
510
			true === $this_item['_invalid']
511
			||
512
			(
513
				$this->original_nav_menu_term_id === $menu->term_id
514
				&&
515
				$current_nav_menu_term_id !== $this->original_nav_menu_term_id
516
			)
517
		);
518
		if ( $should_remove ) {
519
			$filtered_items = array();
520
			foreach ( $items as $item ) {
521
				if ( $item->db_id !== $this->post_id ) {
522
					$filtered_items[] = $item;
523
				}
524
			}
525
			return $filtered_items;
526
		}
527
528
		$mutated = false;
529
		$should_update = (
530
			is_array( $this_item )
531
			&&
532
			$current_nav_menu_term_id === $menu->term_id
533
		);
534
		if ( $should_update ) {
535
			foreach ( $items as $item ) {
536
				if ( $item->db_id === $this->post_id ) {
537
					foreach ( get_object_vars( $this->value_as_wp_post_nav_menu_item() ) as $key => $value ) {
538
						$item->$key = $value;
539
					}
540
					$mutated = true;
541
				}
542
			}
543
544
			// Not found so we have to append it..
545
			if ( ! $mutated ) {
546
				$items[] = $this->value_as_wp_post_nav_menu_item();
547
			}
548
		}
549
550
		return $items;
551
	}
552
553
	/**
554
	 * Re-apply the tail logic also applied on $items by wp_get_nav_menu_items().
555
	 *
556
	 * @since 4.3.0
557
	 * @access public
558
	 * @static
559
	 *
560
	 * @see wp_get_nav_menu_items()
561
	 *
562
	 * @param array  $items An array of menu item post objects.
563
	 * @param object $menu  The menu object.
564
	 * @param array  $args  An array of arguments used to retrieve menu item objects.
565
	 * @return array Array of menu items,
566
	 */
567
	public static function sort_wp_get_nav_menu_items( $items, $menu, $args ) {
568
		// @todo We should probably re-apply some constraints imposed by $args.
569
		unset( $args['include'] );
570
571
		// Remove invalid items only in front end.
572
		if ( ! is_admin() ) {
573
			$items = array_filter( $items, '_is_valid_nav_menu_item' );
574
		}
575
576 View Code Duplication
		if ( ARRAY_A === $args['output'] ) {
577
			$items = wp_list_sort( $items, array(
578
				$args['output_key'] => 'ASC',
579
			) );
580
			$i = 1;
581
582
			foreach ( $items as $k => $item ) {
583
				$items[ $k ]->{$args['output_key']} = $i++;
584
			}
585
		}
586
587
		return $items;
588
	}
589
590
	/**
591
	 * Get the value emulated into a WP_Post and set up as a nav_menu_item.
592
	 *
593
	 * @since 4.3.0
594
	 * @access public
595
	 *
596
	 * @return WP_Post With wp_setup_nav_menu_item() applied.
597
	 */
598
	public function value_as_wp_post_nav_menu_item() {
599
		$item = (object) $this->value();
600
		unset( $item->nav_menu_term_id );
601
602
		$item->post_status = $item->status;
603
		unset( $item->status );
604
605
		$item->post_type = 'nav_menu_item';
606
		$item->menu_order = $item->position;
607
		unset( $item->position );
608
609
		if ( empty( $item->original_title ) ) {
610
			$item->original_title = $this->get_original_title( $item );
611
		}
612
		if ( empty( $item->title ) && ! empty( $item->original_title ) ) {
613
			$item->title = $item->original_title;
614
		}
615
		if ( $item->title ) {
616
			$item->post_title = $item->title;
617
		}
618
619
		$item->ID = $this->post_id;
620
		$item->db_id = $this->post_id;
621
		$post = new WP_Post( (object) $item );
622
623
		if ( empty( $post->post_author ) ) {
624
			$post->post_author = get_current_user_id();
0 ignored issues
show
Documentation Bug introduced by
The property $post_author was declared of type string, but get_current_user_id() is of type integer. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
625
		}
626
627
		if ( ! isset( $post->type_label ) ) {
628
			$post->type_label = $this->get_type_label( $post );
0 ignored issues
show
Bug introduced by
The property type_label does not seem to exist in WP_Post.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
629
		}
630
631
		// Ensure nav menu item URL is set according to linked object.
632
		if ( 'post_type' === $post->type && ! empty( $post->object_id ) ) {
0 ignored issues
show
Documentation introduced by
The property type does not exist on object<WP_Post>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
Documentation introduced by
The property object_id does not exist on object<WP_Post>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
633
			$post->url = get_permalink( $post->object_id );
0 ignored issues
show
Bug introduced by
The property url does not seem to exist in WP_Post.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
Documentation introduced by
The property object_id does not exist on object<WP_Post>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
634
		} elseif ( 'taxonomy' === $post->type && ! empty( $post->object ) && ! empty( $post->object_id ) ) {
0 ignored issues
show
Documentation introduced by
The property type does not exist on object<WP_Post>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
Documentation introduced by
The property object does not exist on object<WP_Post>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
Documentation introduced by
The property object_id does not exist on object<WP_Post>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
635
			$post->url = get_term_link( (int) $post->object_id, $post->object );
0 ignored issues
show
Documentation introduced by
The property object_id does not exist on object<WP_Post>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
Documentation introduced by
The property object does not exist on object<WP_Post>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
636
		} elseif ( 'post_type_archive' === $post->type && ! empty( $post->object ) ) {
0 ignored issues
show
Documentation introduced by
The property type does not exist on object<WP_Post>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
Documentation introduced by
The property object does not exist on object<WP_Post>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
637
			$post->url = get_post_type_archive_link( $post->object );
0 ignored issues
show
Documentation introduced by
The property object does not exist on object<WP_Post>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
638
		}
639
		if ( is_wp_error( $post->url ) ) {
0 ignored issues
show
Documentation introduced by
The property url does not exist on object<WP_Post>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
640
			$post->url = '';
641
		}
642
643
		/** This filter is documented in wp-includes/nav-menu.php */
644
		$post->attr_title = apply_filters( 'nav_menu_attr_title', $post->attr_title );
0 ignored issues
show
Bug introduced by
The property attr_title does not seem to exist in WP_Post.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
Documentation introduced by
The property attr_title does not exist on object<WP_Post>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
645
646
		/** This filter is documented in wp-includes/nav-menu.php */
647
		$post->description = apply_filters( 'nav_menu_description', wp_trim_words( $post->description, 200 ) );
0 ignored issues
show
Bug introduced by
The property description does not seem to exist in WP_Post.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
Documentation introduced by
The property description does not exist on object<WP_Post>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
648
649
		/** This filter is documented in wp-includes/nav-menu.php */
650
		$post = apply_filters( 'wp_setup_nav_menu_item', $post );
651
652
		return $post;
653
	}
654
655
	/**
656
	 * Sanitize an input.
657
	 *
658
	 * Note that parent::sanitize() erroneously does wp_unslash() on $value, but
659
	 * we remove that in this override.
660
	 *
661
	 * @since 4.3.0
662
	 * @access public
663
	 *
664
	 * @param array $menu_item_value The value to sanitize.
665
	 * @return array|false|null Null if an input isn't valid. False if it is marked for deletion.
666
	 *                          Otherwise the sanitized value.
667
	 */
668
	public function sanitize( $menu_item_value ) {
669
		// Menu is marked for deletion.
670
		if ( false === $menu_item_value ) {
671
			return $menu_item_value;
672
		}
673
674
		// Invalid.
675
		if ( ! is_array( $menu_item_value ) ) {
676
			return null;
677
		}
678
679
		$default = array(
680
			'object_id'        => 0,
681
			'object'           => '',
682
			'menu_item_parent' => 0,
683
			'position'         => 0,
684
			'type'             => 'custom',
685
			'title'            => '',
686
			'url'              => '',
687
			'target'           => '',
688
			'attr_title'       => '',
689
			'description'      => '',
690
			'classes'          => '',
691
			'xfn'              => '',
692
			'status'           => 'publish',
693
			'original_title'   => '',
694
			'nav_menu_term_id' => 0,
695
			'_invalid'         => false,
696
		);
697
		$menu_item_value = array_merge( $default, $menu_item_value );
698
		$menu_item_value = wp_array_slice_assoc( $menu_item_value, array_keys( $default ) );
699
		$menu_item_value['position'] = intval( $menu_item_value['position'] );
700
701
		foreach ( array( 'object_id', 'menu_item_parent', 'nav_menu_term_id' ) as $key ) {
702
			// Note we need to allow negative-integer IDs for previewed objects not inserted yet.
703
			$menu_item_value[ $key ] = intval( $menu_item_value[ $key ] );
704
		}
705
706
		foreach ( array( 'type', 'object', 'target' ) as $key ) {
707
			$menu_item_value[ $key ] = sanitize_key( $menu_item_value[ $key ] );
708
		}
709
710
		foreach ( array( 'xfn', 'classes' ) as $key ) {
711
			$value = $menu_item_value[ $key ];
712
			if ( ! is_array( $value ) ) {
713
				$value = explode( ' ', $value );
714
			}
715
			$menu_item_value[ $key ] = implode( ' ', array_map( 'sanitize_html_class', $value ) );
716
		}
717
718
		$menu_item_value['original_title'] = sanitize_text_field( $menu_item_value['original_title'] );
719
720
		// Apply the same filters as when calling wp_insert_post().
721
		$menu_item_value['title'] = wp_unslash( apply_filters( 'title_save_pre', wp_slash( $menu_item_value['title'] ) ) );
722
		$menu_item_value['attr_title'] = wp_unslash( apply_filters( 'excerpt_save_pre', wp_slash( $menu_item_value['attr_title'] ) ) );
723
		$menu_item_value['description'] = wp_unslash( apply_filters( 'content_save_pre', wp_slash( $menu_item_value['description'] ) ) );
724
725
		$menu_item_value['url'] = esc_url_raw( $menu_item_value['url'] );
726
		if ( 'publish' !== $menu_item_value['status'] ) {
727
			$menu_item_value['status'] = 'draft';
728
		}
729
730
		$menu_item_value['_invalid'] = (bool) $menu_item_value['_invalid'];
731
732
		/** This filter is documented in wp-includes/class-wp-customize-setting.php */
733
		return apply_filters( "customize_sanitize_{$this->id}", $menu_item_value, $this );
734
	}
735
736
	/**
737
	 * Creates/updates the nav_menu_item post for this setting.
738
	 *
739
	 * Any created menu items will have their assigned post IDs exported to the client
740
	 * via the {@see 'customize_save_response'} filter. Likewise, any errors will be
741
	 * exported to the client via the customize_save_response() filter.
742
	 *
743
	 * To delete a menu, the client can send false as the value.
744
	 *
745
	 * @since 4.3.0
746
	 * @access protected
747
	 *
748
	 * @see wp_update_nav_menu_item()
749
	 *
750
	 * @param array|false $value The menu item array to update. If false, then the menu item will be deleted
751
	 *                           entirely. See WP_Customize_Nav_Menu_Item_Setting::$default for what the value
752
	 *                           should consist of.
753
	 * @return null|void
754
	 */
755
	protected function update( $value ) {
756
		if ( $this->is_updated ) {
757
			return;
758
		}
759
760
		$this->is_updated = true;
761
		$is_placeholder   = ( $this->post_id < 0 );
762
		$is_delete        = ( false === $value );
763
764
		// Update the cached value.
765
		$this->value = $value;
0 ignored issues
show
Documentation Bug introduced by
It seems like $value can also be of type false. However, the property $value is declared as type array. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
766
767
		add_filter( 'customize_save_response', array( $this, 'amend_customize_save_response' ) );
768
769
		if ( $is_delete ) {
770
			// If the current setting post is a placeholder, a delete request is a no-op.
771
			if ( $is_placeholder ) {
772
				$this->update_status = 'deleted';
773
			} else {
774
				$r = wp_delete_post( $this->post_id, true );
775
776
				if ( false === $r ) {
777
					$this->update_error  = new WP_Error( 'delete_failure' );
778
					$this->update_status = 'error';
779
				} else {
780
					$this->update_status = 'deleted';
781
				}
782
				// @todo send back the IDs for all associated nav menu items deleted, so these settings (and controls) can be removed from Customizer?
783
			}
784
		} else {
785
786
			// Handle saving menu items for menus that are being newly-created.
787 View Code Duplication
			if ( $value['nav_menu_term_id'] < 0 ) {
788
				$nav_menu_setting_id = sprintf( 'nav_menu[%s]', $value['nav_menu_term_id'] );
789
				$nav_menu_setting    = $this->manager->get_setting( $nav_menu_setting_id );
790
791
				if ( ! $nav_menu_setting || ! ( $nav_menu_setting instanceof WP_Customize_Nav_Menu_Setting ) ) {
792
					$this->update_status = 'error';
793
					$this->update_error  = new WP_Error( 'unexpected_nav_menu_setting' );
794
					return;
795
				}
796
797
				if ( false === $nav_menu_setting->save() ) {
798
					$this->update_status = 'error';
799
					$this->update_error  = new WP_Error( 'nav_menu_setting_failure' );
800
					return;
801
				}
802
803
				if ( $nav_menu_setting->previous_term_id !== intval( $value['nav_menu_term_id'] ) ) {
804
					$this->update_status = 'error';
805
					$this->update_error  = new WP_Error( 'unexpected_previous_term_id' );
806
					return;
807
				}
808
809
				$value['nav_menu_term_id'] = $nav_menu_setting->term_id;
810
			}
811
812
			// Handle saving a nav menu item that is a child of a nav menu item being newly-created.
813 View Code Duplication
			if ( $value['menu_item_parent'] < 0 ) {
814
				$parent_nav_menu_item_setting_id = sprintf( 'nav_menu_item[%s]', $value['menu_item_parent'] );
815
				$parent_nav_menu_item_setting    = $this->manager->get_setting( $parent_nav_menu_item_setting_id );
816
817
				if ( ! $parent_nav_menu_item_setting || ! ( $parent_nav_menu_item_setting instanceof WP_Customize_Nav_Menu_Item_Setting ) ) {
818
					$this->update_status = 'error';
819
					$this->update_error  = new WP_Error( 'unexpected_nav_menu_item_setting' );
820
					return;
821
				}
822
823
				if ( false === $parent_nav_menu_item_setting->save() ) {
824
					$this->update_status = 'error';
825
					$this->update_error  = new WP_Error( 'nav_menu_item_setting_failure' );
826
					return;
827
				}
828
829
				if ( $parent_nav_menu_item_setting->previous_post_id !== intval( $value['menu_item_parent'] ) ) {
830
					$this->update_status = 'error';
831
					$this->update_error  = new WP_Error( 'unexpected_previous_post_id' );
832
					return;
833
				}
834
835
				$value['menu_item_parent'] = $parent_nav_menu_item_setting->post_id;
836
			}
837
838
			// Insert or update menu.
839
			$menu_item_data = array(
840
				'menu-item-object-id'   => $value['object_id'],
841
				'menu-item-object'      => $value['object'],
842
				'menu-item-parent-id'   => $value['menu_item_parent'],
843
				'menu-item-position'    => $value['position'],
844
				'menu-item-type'        => $value['type'],
845
				'menu-item-title'       => $value['title'],
846
				'menu-item-url'         => $value['url'],
847
				'menu-item-description' => $value['description'],
848
				'menu-item-attr-title'  => $value['attr_title'],
849
				'menu-item-target'      => $value['target'],
850
				'menu-item-classes'     => $value['classes'],
851
				'menu-item-xfn'         => $value['xfn'],
852
				'menu-item-status'      => $value['status'],
853
			);
854
855
			$r = wp_update_nav_menu_item(
856
				$value['nav_menu_term_id'],
857
				$is_placeholder ? 0 : $this->post_id,
858
				wp_slash( $menu_item_data )
0 ignored issues
show
Bug introduced by
It seems like wp_slash($menu_item_data) targeting wp_slash() can also be of type string; however, wp_update_nav_menu_item() does only seem to accept array, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
859
			);
860
861 View Code Duplication
			if ( is_wp_error( $r ) ) {
862
				$this->update_status = 'error';
863
				$this->update_error = $r;
864
			} else {
865
				if ( $is_placeholder ) {
866
					$this->previous_post_id = $this->post_id;
867
					$this->post_id = $r;
868
					$this->update_status = 'inserted';
869
				} else {
870
					$this->update_status = 'updated';
871
				}
872
			}
873
		}
874
875
	}
876
877
	/**
878
	 * Export data for the JS client.
879
	 *
880
	 * @since 4.3.0
881
	 * @access public
882
	 *
883
	 * @see WP_Customize_Nav_Menu_Item_Setting::update()
884
	 *
885
	 * @param array $data Additional information passed back to the 'saved' event on `wp.customize`.
886
	 * @return array Save response data.
887
	 */
888
	public function amend_customize_save_response( $data ) {
889
		if ( ! isset( $data['nav_menu_item_updates'] ) ) {
890
			$data['nav_menu_item_updates'] = array();
891
		}
892
893
		$data['nav_menu_item_updates'][] = array(
894
			'post_id'          => $this->post_id,
895
			'previous_post_id' => $this->previous_post_id,
896
			'error'            => $this->update_error ? $this->update_error->get_error_code() : null,
897
			'status'           => $this->update_status,
898
		);
899
		return $data;
900
	}
901
}
902