WP_Customize_Nav_Menu_Setting   C
last analyzed

Complexity

Total Complexity 72

Size/Duplication

Total Lines 636
Duplicated Lines 2.2 %

Coupling/Cohesion

Components 2
Dependencies 4

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 14
loc 636
rs 5.3781
wmc 72
lcom 2
cbo 4

11 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 13 3
D value() 0 35 9
D filter_wp_get_nav_menus() 0 46 10
A _sort_menus_by_orderby() 0 4 1
B filter_wp_get_nav_menu_object() 0 36 6
A filter_nav_menu_options() 0 14 3
B sanitize() 0 32 4
F update() 14 103 21
B filter_nav_menu_options_value() 0 15 6
B amend_customize_save_response() 0 24 5
B preview() 0 23 4

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

1
<?php
2
/**
3
 * Customize API: WP_Customize_Nav_Menu_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_get_nav_menu_object()
19
 * @see WP_Customize_Setting
20
 */
21
class WP_Customize_Nav_Menu_Setting extends WP_Customize_Setting {
22
23
	const ID_PATTERN = '/^nav_menu\[(?P<id>-?\d+)\]$/';
24
25
	const TAXONOMY = 'nav_menu';
26
27
	const TYPE = 'nav_menu';
28
29
	/**
30
	 * Setting type.
31
	 *
32
	 * @since 4.3.0
33
	 * @access public
34
	 * @var string
35
	 */
36
	public $type = self::TYPE;
37
38
	/**
39
	 * Default setting value.
40
	 *
41
	 * @since 4.3.0
42
	 * @access public
43
	 * @var array
44
	 *
45
	 * @see wp_get_nav_menu_object()
46
	 */
47
	public $default = array(
48
		'name'        => '',
49
		'description' => '',
50
		'parent'      => 0,
51
		'auto_add'    => false,
52
	);
53
54
	/**
55
	 * Default transport.
56
	 *
57
	 * @since 4.3.0
58
	 * @access public
59
	 * @var string
60
	 */
61
	public $transport = 'postMessage';
62
63
	/**
64
	 * The term ID represented by this setting instance.
65
	 *
66
	 * A negative value represents a placeholder ID for a new menu not yet saved.
67
	 *
68
	 * @since 4.3.0
69
	 * @access public
70
	 * @var int
71
	 */
72
	public $term_id;
73
74
	/**
75
	 * Previous (placeholder) term ID used before creating a new menu.
76
	 *
77
	 * This value will be exported to JS via the {@see 'customize_save_response'} filter
78
	 * so that JavaScript can update the settings to refer to the newly-assigned
79
	 * term ID. This value is always negative to indicate it does not refer to
80
	 * a real term.
81
	 *
82
	 * @since 4.3.0
83
	 * @access public
84
	 * @var int
85
	 *
86
	 * @see WP_Customize_Nav_Menu_Setting::update()
87
	 * @see WP_Customize_Nav_Menu_Setting::amend_customize_save_response()
88
	 */
89
	public $previous_term_id;
90
91
	/**
92
	 * Whether or not update() was called.
93
	 *
94
	 * @since 4.3.0
95
	 * @access protected
96
	 * @var bool
97
	 */
98
	protected $is_updated = false;
99
100
	/**
101
	 * Status for calling the update method, used in customize_save_response filter.
102
	 *
103
	 * See {@see 'customize_save_response'}.
104
	 *
105
	 * When status is inserted, the placeholder term ID is stored in `$previous_term_id`.
106
	 * When status is error, the error is stored in `$update_error`.
107
	 *
108
	 * @since 4.3.0
109
	 * @access public
110
	 * @var string updated|inserted|deleted|error
111
	 *
112
	 * @see WP_Customize_Nav_Menu_Setting::update()
113
	 * @see WP_Customize_Nav_Menu_Setting::amend_customize_save_response()
114
	 */
115
	public $update_status;
116
117
	/**
118
	 * Any error object returned by wp_update_nav_menu_object() when setting is updated.
119
	 *
120
	 * @since 4.3.0
121
	 * @access public
122
	 * @var WP_Error
123
	 *
124
	 * @see WP_Customize_Nav_Menu_Setting::update()
125
	 * @see WP_Customize_Nav_Menu_Setting::amend_customize_save_response()
126
	 */
127
	public $update_error;
128
129
	/**
130
	 * Constructor.
131
	 *
132
	 * Any supplied $args override class property defaults.
133
	 *
134
	 * @since 4.3.0
135
	 * @access public
136
	 *
137
	 * @param WP_Customize_Manager $manager Bootstrap Customizer instance.
138
	 * @param string               $id      An specific ID of the setting. Can be a
139
	 *                                      theme mod or option name.
140
	 * @param array                $args    Optional. Setting arguments.
141
	 *
142
	 * @throws Exception If $id is not valid for this setting type.
143
	 */
144
	public function __construct( WP_Customize_Manager $manager, $id, array $args = array() ) {
145
		if ( empty( $manager->nav_menus ) ) {
146
			throw new Exception( 'Expected WP_Customize_Manager::$nav_menus to be set.' );
147
		}
148
149
		if ( ! preg_match( self::ID_PATTERN, $id, $matches ) ) {
150
			throw new Exception( "Illegal widget setting ID: $id" );
151
		}
152
153
		$this->term_id = intval( $matches['id'] );
154
155
		parent::__construct( $manager, $id, $args );
156
	}
157
158
	/**
159
	 * Get the instance data for a given widget setting.
160
	 *
161
	 * @since 4.3.0
162
	 * @access public
163
	 *
164
	 * @see wp_get_nav_menu_object()
165
	 *
166
	 * @return array Instance data.
167
	 */
168
	public function value() {
169
		if ( $this->is_previewed && $this->_previewed_blog_id === get_current_blog_id() ) {
170
			$undefined  = new stdClass(); // Symbol.
171
			$post_value = $this->post_value( $undefined );
172
173
			if ( $undefined === $post_value ) {
174
				$value = $this->_original_value;
175
			} else {
176
				$value = $post_value;
177
			}
178
		} else {
179
			$value = false;
180
181
			// Note that a term_id of less than one indicates a nav_menu not yet inserted.
182
			if ( $this->term_id > 0 ) {
183
				$term = wp_get_nav_menu_object( $this->term_id );
184
185
				if ( $term ) {
186
					$value = wp_array_slice_assoc( (array) $term, array_keys( $this->default ) );
187
188
					$nav_menu_options  = (array) get_option( 'nav_menu_options', array() );
189
					$value['auto_add'] = false;
190
191
					if ( isset( $nav_menu_options['auto_add'] ) && is_array( $nav_menu_options['auto_add'] ) ) {
192
						$value['auto_add'] = in_array( $term->term_id, $nav_menu_options['auto_add'] );
193
					}
194
				}
195
			}
196
197
			if ( ! is_array( $value ) ) {
198
				$value = $this->default;
199
			}
200
		}
201
		return $value;
202
	}
203
204
	/**
205
	 * Handle previewing the setting.
206
	 *
207
	 * @since 4.3.0
208
	 * @since 4.4.0 Added boolean return value
209
	 * @access public
210
	 *
211
	 * @see WP_Customize_Manager::post_value()
212
	 *
213
	 * @return bool False if method short-circuited due to no-op.
214
	 */
215
	public function preview() {
216
		if ( $this->is_previewed ) {
217
			return false;
218
		}
219
220
		$undefined = new stdClass();
221
		$is_placeholder = ( $this->term_id < 0 );
222
		$is_dirty = ( $undefined !== $this->post_value( $undefined ) );
223
		if ( ! $is_placeholder && ! $is_dirty ) {
224
			return false;
225
		}
226
227
		$this->is_previewed       = true;
228
		$this->_original_value    = $this->value();
229
		$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...
230
231
		add_filter( 'wp_get_nav_menus', array( $this, 'filter_wp_get_nav_menus' ), 10, 2 );
232
		add_filter( 'wp_get_nav_menu_object', array( $this, 'filter_wp_get_nav_menu_object' ), 10, 2 );
233
		add_filter( 'default_option_nav_menu_options', array( $this, 'filter_nav_menu_options' ) );
234
		add_filter( 'option_nav_menu_options', array( $this, 'filter_nav_menu_options' ) );
235
236
		return true;
237
	}
238
239
	/**
240
	 * Filters the wp_get_nav_menus() result to ensure the inserted menu object is included, and the deleted one is removed.
241
	 *
242
	 * @since 4.3.0
243
	 * @access public
244
	 *
245
	 * @see wp_get_nav_menus()
246
	 *
247
	 * @param array $menus An array of menu objects.
248
	 * @param array $args  An array of arguments used to retrieve menu objects.
249
	 * @return array
250
	 */
251
	public function filter_wp_get_nav_menus( $menus, $args ) {
252
		if ( get_current_blog_id() !== $this->_previewed_blog_id ) {
253
			return $menus;
254
		}
255
256
		$setting_value = $this->value();
257
		$is_delete = ( false === $setting_value );
258
		$index = -1;
259
260
		// Find the existing menu item's position in the list.
261
		foreach ( $menus as $i => $menu ) {
262
			if ( (int) $this->term_id === (int) $menu->term_id || (int) $this->previous_term_id === (int) $menu->term_id ) {
263
				$index = $i;
264
				break;
265
			}
266
		}
267
268
		if ( $is_delete ) {
269
			// Handle deleted menu by removing it from the list.
270
			if ( -1 !== $index ) {
271
				array_splice( $menus, $index, 1 );
272
			}
273
		} else {
274
			// Handle menus being updated or inserted.
275
			$menu_obj = (object) array_merge( array(
276
				'term_id'          => $this->term_id,
277
				'term_taxonomy_id' => $this->term_id,
278
				'slug'             => sanitize_title( $setting_value['name'] ),
279
				'count'            => 0,
280
				'term_group'       => 0,
281
				'taxonomy'         => self::TAXONOMY,
282
				'filter'           => 'raw',
283
			), $setting_value );
284
285
			array_splice( $menus, $index, ( -1 === $index ? 0 : 1 ), array( $menu_obj ) );
286
		}
287
288
		// Make sure the menu objects get re-sorted after an update/insert.
289
		if ( ! $is_delete && ! empty( $args['orderby'] ) ) {
290
			$this->_current_menus_sort_orderby = $args['orderby'];
291
			usort( $menus, array( $this, '_sort_menus_by_orderby' ) );
292
		}
293
		// @todo add support for $args['hide_empty'] === true
294
295
		return $menus;
296
	}
297
298
	/**
299
	 * Temporary non-closure passing of orderby value to function.
300
	 *
301
	 * @since 4.3.0
302
	 * @access protected
303
	 * @var string
304
	 *
305
	 * @see WP_Customize_Nav_Menu_Setting::filter_wp_get_nav_menus()
306
	 * @see WP_Customize_Nav_Menu_Setting::_sort_menus_by_orderby()
307
	 */
308
	protected $_current_menus_sort_orderby;
309
310
	/**
311
	 * Sort menu objects by the class-supplied orderby property.
312
	 *
313
	 * This is a workaround for a lack of closures.
314
	 *
315
	 * @since 4.3.0
316
	 * @access protected
317
	 * @param object $menu1
318
	 * @param object $menu2
319
	 * @return int
320
	 *
321
	 * @see WP_Customize_Nav_Menu_Setting::filter_wp_get_nav_menus()
322
	 */
323
	protected function _sort_menus_by_orderby( $menu1, $menu2 ) {
324
		$key = $this->_current_menus_sort_orderby;
325
		return strcmp( $menu1->$key, $menu2->$key );
326
	}
327
328
	/**
329
	 * Filters the wp_get_nav_menu_object() result to supply the previewed menu object.
330
	 *
331
	 * Requesting a nav_menu object by anything but ID is not supported.
332
	 *
333
	 * @since 4.3.0
334
	 * @access public
335
	 *
336
	 * @see wp_get_nav_menu_object()
337
	 *
338
	 * @param object|null $menu_obj Object returned by wp_get_nav_menu_object().
339
	 * @param string      $menu_id  ID of the nav_menu term. Requests by slug or name will be ignored.
340
	 * @return object|null
341
	 */
342
	public function filter_wp_get_nav_menu_object( $menu_obj, $menu_id ) {
343
		$ok = (
344
			get_current_blog_id() === $this->_previewed_blog_id
345
			&&
346
			is_int( $menu_id )
347
			&&
348
			$menu_id === $this->term_id
349
		);
350
		if ( ! $ok ) {
351
			return $menu_obj;
352
		}
353
354
		$setting_value = $this->value();
355
356
		// Handle deleted menus.
357
		if ( false === $setting_value ) {
358
			return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by WP_Customize_Nav_Menu_Se..._wp_get_nav_menu_object of type object|null.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

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

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
359
		}
360
361
		// Handle sanitization failure by preventing short-circuiting.
362
		if ( null === $setting_value ) {
363
			return $menu_obj;
364
		}
365
366
		$menu_obj = (object) array_merge( array(
367
				'term_id'          => $this->term_id,
368
				'term_taxonomy_id' => $this->term_id,
369
				'slug'             => sanitize_title( $setting_value['name'] ),
370
				'count'            => 0,
371
				'term_group'       => 0,
372
				'taxonomy'         => self::TAXONOMY,
373
				'filter'           => 'raw',
374
			), $setting_value );
375
376
		return $menu_obj;
377
	}
378
379
	/**
380
	 * Filters the nav_menu_options option to include this menu's auto_add preference.
381
	 *
382
	 * @since 4.3.0
383
	 * @access public
384
	 *
385
	 * @param array $nav_menu_options Nav menu options including auto_add.
386
	 * @return array (Kaybe) modified nav menu options.
387
	 */
388
	public function filter_nav_menu_options( $nav_menu_options ) {
389
		if ( $this->_previewed_blog_id !== get_current_blog_id() ) {
390
			return $nav_menu_options;
391
		}
392
393
		$menu = $this->value();
394
		$nav_menu_options = $this->filter_nav_menu_options_value(
395
			$nav_menu_options,
396
			$this->term_id,
397
			false === $menu ? false : $menu['auto_add']
398
		);
399
400
		return $nav_menu_options;
401
	}
402
403
	/**
404
	 * Sanitize an input.
405
	 *
406
	 * Note that parent::sanitize() erroneously does wp_unslash() on $value, but
407
	 * we remove that in this override.
408
	 *
409
	 * @since 4.3.0
410
	 * @access public
411
	 *
412
	 * @param array $value The value to sanitize.
413
	 * @return array|false|null Null if an input isn't valid. False if it is marked for deletion.
414
	 *                          Otherwise the sanitized value.
415
	 */
416
	public function sanitize( $value ) {
417
		// Menu is marked for deletion.
418
		if ( false === $value ) {
419
			return $value;
420
		}
421
422
		// Invalid.
423
		if ( ! is_array( $value ) ) {
424
			return null;
425
		}
426
427
		$default = array(
428
			'name'        => '',
429
			'description' => '',
430
			'parent'      => 0,
431
			'auto_add'    => false,
432
		);
433
		$value = array_merge( $default, $value );
434
		$value = wp_array_slice_assoc( $value, array_keys( $default ) );
435
436
		$value['name']        = trim( esc_html( $value['name'] ) ); // This sanitization code is used in wp-admin/nav-menus.php.
437
		$value['description'] = sanitize_text_field( $value['description'] );
438
		$value['parent']      = max( 0, intval( $value['parent'] ) );
439
		$value['auto_add']    = ! empty( $value['auto_add'] );
440
441
		if ( '' === $value['name'] ) {
442
			$value['name'] = _x( '(unnamed)', 'Missing menu name.' );
443
		}
444
445
		/** This filter is documented in wp-includes/class-wp-customize-setting.php */
446
		return apply_filters( "customize_sanitize_{$this->id}", $value, $this );
447
	}
448
449
	/**
450
	 * Storage for data to be sent back to client in customize_save_response filter.
451
	 *
452
	 * See {@see 'customize_save_response'}.
453
	 *
454
	 * @access protected
455
	 * @since 4.3.0
456
	 * @var array
457
	 *
458
	 * @see WP_Customize_Nav_Menu_Setting::amend_customize_save_response()
459
	 */
460
	protected $_widget_nav_menu_updates = array();
461
462
	/**
463
	 * Create/update the nav_menu term for this setting.
464
	 *
465
	 * Any created menus will have their assigned term IDs exported to the client
466
	 * via the {@see 'customize_save_response'} filter. Likewise, any errors will be exported
467
	 * to the client via the customize_save_response() filter.
468
	 *
469
	 * To delete a menu, the client can send false as the value.
470
	 *
471
	 * @since 4.3.0
472
	 * @access protected
473
	 *
474
	 * @see wp_update_nav_menu_object()
475
	 *
476
	 * @param array|false $value {
477
	 *     The value to update. Note that slug cannot be updated via wp_update_nav_menu_object().
478
	 *     If false, then the menu will be deleted entirely.
479
	 *
480
	 *     @type string $name        The name of the menu to save.
481
	 *     @type string $description The term description. Default empty string.
482
	 *     @type int    $parent      The id of the parent term. Default 0.
483
	 *     @type bool   $auto_add    Whether pages will auto_add to this menu. Default false.
484
	 * }
485
	 * @return null|void
486
	 */
487
	protected function update( $value ) {
488
		if ( $this->is_updated ) {
489
			return;
490
		}
491
492
		$this->is_updated = true;
493
		$is_placeholder   = ( $this->term_id < 0 );
494
		$is_delete        = ( false === $value );
495
496
		add_filter( 'customize_save_response', array( $this, 'amend_customize_save_response' ) );
497
498
		$auto_add = null;
499
		if ( $is_delete ) {
500
			// If the current setting term is a placeholder, a delete request is a no-op.
501
			if ( $is_placeholder ) {
502
				$this->update_status = 'deleted';
503
			} else {
504
				$r = wp_delete_nav_menu( $this->term_id );
505
506
				if ( is_wp_error( $r ) ) {
507
					$this->update_status = 'error';
508
					$this->update_error  = $r;
0 ignored issues
show
Documentation Bug introduced by
It seems like $r can also be of type boolean or integer. However, the property $update_error is declared as type object<WP_Error>. 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...
509
				} else {
510
					$this->update_status = 'deleted';
511
					$auto_add = false;
512
				}
513
			}
514
		} else {
515
			// Insert or update menu.
516
			$menu_data = wp_array_slice_assoc( $value, array( 'description', 'parent' ) );
517
			$menu_data['menu-name'] = $value['name'];
518
519
			$menu_id = $is_placeholder ? 0 : $this->term_id;
520
			$r = wp_update_nav_menu_object( $menu_id, wp_slash( $menu_data ) );
0 ignored issues
show
Bug introduced by
It seems like wp_slash($menu_data) targeting wp_slash() can also be of type string; however, wp_update_nav_menu_object() 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...
521
			$original_name = $menu_data['menu-name'];
522
			$name_conflict_suffix = 1;
523
			while ( is_wp_error( $r ) && 'menu_exists' === $r->get_error_code() ) {
0 ignored issues
show
Bug introduced by
The method get_error_code does only exist in WP_Error, but not in WP_Term.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
524
				$name_conflict_suffix += 1;
525
				/* translators: 1: original menu name, 2: duplicate count */
526
				$menu_data['menu-name'] = sprintf( __( '%1$s (%2$d)' ), $original_name, $name_conflict_suffix );
527
				$r = wp_update_nav_menu_object( $menu_id, wp_slash( $menu_data ) );
0 ignored issues
show
Bug introduced by
It seems like wp_slash($menu_data) targeting wp_slash() can also be of type string; however, wp_update_nav_menu_object() 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...
528
			}
529
530 View Code Duplication
			if ( is_wp_error( $r ) ) {
531
				$this->update_status = 'error';
532
				$this->update_error  = $r;
533
			} else {
534
				if ( $is_placeholder ) {
535
					$this->previous_term_id = $this->term_id;
536
					$this->term_id          = $r;
537
					$this->update_status    = 'inserted';
538
				} else {
539
					$this->update_status = 'updated';
540
				}
541
542
				$auto_add = $value['auto_add'];
543
			}
544
		}
545
546
		if ( null !== $auto_add ) {
547
			$nav_menu_options = $this->filter_nav_menu_options_value(
548
				(array) get_option( 'nav_menu_options', array() ),
549
				$this->term_id,
550
				$auto_add
551
			);
552
			update_option( 'nav_menu_options', $nav_menu_options );
553
		}
554
555
		if ( 'inserted' === $this->update_status ) {
556
			// Make sure that new menus assigned to nav menu locations use their new IDs.
557
			foreach ( $this->manager->settings() as $setting ) {
558
				if ( ! preg_match( '/^nav_menu_locations\[/', $setting->id ) ) {
559
					continue;
560
				}
561
562
				$post_value = $setting->post_value( null );
563
				if ( ! is_null( $post_value ) && $this->previous_term_id === intval( $post_value ) ) {
564
					$this->manager->set_post_value( $setting->id, $this->term_id );
565
					$setting->save();
566
				}
567
			}
568
569
			// Make sure that any nav_menu widgets referencing the placeholder nav menu get updated and sent back to client.
570
			foreach ( array_keys( $this->manager->unsanitized_post_values() ) as $setting_id ) {
571
				$nav_menu_widget_setting = $this->manager->get_setting( $setting_id );
572
				if ( ! $nav_menu_widget_setting || ! preg_match( '/^widget_nav_menu\[/', $nav_menu_widget_setting->id ) ) {
573
					continue;
574
				}
575
576
				$widget_instance = $nav_menu_widget_setting->post_value(); // Note that this calls WP_Customize_Widgets::sanitize_widget_instance().
577
				if ( empty( $widget_instance['nav_menu'] ) || intval( $widget_instance['nav_menu'] ) !== $this->previous_term_id ) {
578
					continue;
579
				}
580
581
				$widget_instance['nav_menu'] = $this->term_id;
582
				$updated_widget_instance = $this->manager->widgets->sanitize_widget_js_instance( $widget_instance );
583
				$this->manager->set_post_value( $nav_menu_widget_setting->id, $updated_widget_instance );
584
				$nav_menu_widget_setting->save();
585
586
				$this->_widget_nav_menu_updates[ $nav_menu_widget_setting->id ] = $updated_widget_instance;
587
			}
588
		}
589
	}
590
591
	/**
592
	 * Updates a nav_menu_options array.
593
	 *
594
	 * @since 4.3.0
595
	 * @access protected
596
	 *
597
	 * @see WP_Customize_Nav_Menu_Setting::filter_nav_menu_options()
598
	 * @see WP_Customize_Nav_Menu_Setting::update()
599
	 *
600
	 * @param array $nav_menu_options Array as returned by get_option( 'nav_menu_options' ).
601
	 * @param int   $menu_id          The term ID for the given menu.
602
	 * @param bool  $auto_add         Whether to auto-add or not.
603
	 * @return array (Maybe) modified nav_menu_otions array.
604
	 */
605
	protected function filter_nav_menu_options_value( $nav_menu_options, $menu_id, $auto_add ) {
606
		$nav_menu_options = (array) $nav_menu_options;
607
		if ( ! isset( $nav_menu_options['auto_add'] ) ) {
608
			$nav_menu_options['auto_add'] = array();
609
		}
610
611
		$i = array_search( $menu_id, $nav_menu_options['auto_add'] );
612
		if ( $auto_add && false === $i ) {
613
			array_push( $nav_menu_options['auto_add'], $this->term_id );
614
		} elseif ( ! $auto_add && false !== $i ) {
615
			array_splice( $nav_menu_options['auto_add'], $i, 1 );
616
		}
617
618
		return $nav_menu_options;
619
	}
620
621
	/**
622
	 * Export data for the JS client.
623
	 *
624
	 * @since 4.3.0
625
	 * @access public
626
	 *
627
	 * @see WP_Customize_Nav_Menu_Setting::update()
628
	 *
629
	 * @param array $data Additional information passed back to the 'saved' event on `wp.customize`.
630
	 * @return array Export data.
631
	 */
632
	public function amend_customize_save_response( $data ) {
633
		if ( ! isset( $data['nav_menu_updates'] ) ) {
634
			$data['nav_menu_updates'] = array();
635
		}
636
		if ( ! isset( $data['widget_nav_menu_updates'] ) ) {
637
			$data['widget_nav_menu_updates'] = array();
638
		}
639
640
		$data['nav_menu_updates'][] = array(
641
			'term_id'          => $this->term_id,
642
			'previous_term_id' => $this->previous_term_id,
643
			'error'            => $this->update_error ? $this->update_error->get_error_code() : null,
644
			'status'           => $this->update_status,
645
			'saved_value'      => 'deleted' === $this->update_status ? null : $this->value(),
646
		);
647
648
		$data['widget_nav_menu_updates'] = array_merge(
649
			$data['widget_nav_menu_updates'],
650
			$this->_widget_nav_menu_updates
651
		);
652
		$this->_widget_nav_menu_updates = array();
653
654
		return $data;
655
	}
656
}
657