wp/wp-includes/customize/class-wp-customize-nav-menu-setting.php
changeset 7 cf61fcea0001
child 9 177826044cd9
equal deleted inserted replaced
6:490d5cc509ed 7:cf61fcea0001
       
     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 	 * @var string
       
    34 	 */
       
    35 	public $type = self::TYPE;
       
    36 
       
    37 	/**
       
    38 	 * Default setting value.
       
    39 	 *
       
    40 	 * @since 4.3.0
       
    41 	 * @var array
       
    42 	 *
       
    43 	 * @see wp_get_nav_menu_object()
       
    44 	 */
       
    45 	public $default = array(
       
    46 		'name'        => '',
       
    47 		'description' => '',
       
    48 		'parent'      => 0,
       
    49 		'auto_add'    => false,
       
    50 	);
       
    51 
       
    52 	/**
       
    53 	 * Default transport.
       
    54 	 *
       
    55 	 * @since 4.3.0
       
    56 	 * @var string
       
    57 	 */
       
    58 	public $transport = 'postMessage';
       
    59 
       
    60 	/**
       
    61 	 * The term ID represented by this setting instance.
       
    62 	 *
       
    63 	 * A negative value represents a placeholder ID for a new menu not yet saved.
       
    64 	 *
       
    65 	 * @since 4.3.0
       
    66 	 * @var int
       
    67 	 */
       
    68 	public $term_id;
       
    69 
       
    70 	/**
       
    71 	 * Previous (placeholder) term ID used before creating a new menu.
       
    72 	 *
       
    73 	 * This value will be exported to JS via the {@see 'customize_save_response'} filter
       
    74 	 * so that JavaScript can update the settings to refer to the newly-assigned
       
    75 	 * term ID. This value is always negative to indicate it does not refer to
       
    76 	 * a real term.
       
    77 	 *
       
    78 	 * @since 4.3.0
       
    79 	 * @var int
       
    80 	 *
       
    81 	 * @see WP_Customize_Nav_Menu_Setting::update()
       
    82 	 * @see WP_Customize_Nav_Menu_Setting::amend_customize_save_response()
       
    83 	 */
       
    84 	public $previous_term_id;
       
    85 
       
    86 	/**
       
    87 	 * Whether or not update() was called.
       
    88 	 *
       
    89 	 * @since 4.3.0
       
    90 	 * @var bool
       
    91 	 */
       
    92 	protected $is_updated = false;
       
    93 
       
    94 	/**
       
    95 	 * Status for calling the update method, used in customize_save_response filter.
       
    96 	 *
       
    97 	 * See {@see 'customize_save_response'}.
       
    98 	 *
       
    99 	 * When status is inserted, the placeholder term ID is stored in `$previous_term_id`.
       
   100 	 * When status is error, the error is stored in `$update_error`.
       
   101 	 *
       
   102 	 * @since 4.3.0
       
   103 	 * @var string updated|inserted|deleted|error
       
   104 	 *
       
   105 	 * @see WP_Customize_Nav_Menu_Setting::update()
       
   106 	 * @see WP_Customize_Nav_Menu_Setting::amend_customize_save_response()
       
   107 	 */
       
   108 	public $update_status;
       
   109 
       
   110 	/**
       
   111 	 * Any error object returned by wp_update_nav_menu_object() when setting is updated.
       
   112 	 *
       
   113 	 * @since 4.3.0
       
   114 	 * @var WP_Error
       
   115 	 *
       
   116 	 * @see WP_Customize_Nav_Menu_Setting::update()
       
   117 	 * @see WP_Customize_Nav_Menu_Setting::amend_customize_save_response()
       
   118 	 */
       
   119 	public $update_error;
       
   120 
       
   121 	/**
       
   122 	 * Constructor.
       
   123 	 *
       
   124 	 * Any supplied $args override class property defaults.
       
   125 	 *
       
   126 	 * @since 4.3.0
       
   127 	 *
       
   128 	 * @param WP_Customize_Manager $manager Bootstrap Customizer instance.
       
   129 	 * @param string               $id      An specific ID of the setting. Can be a
       
   130 	 *                                      theme mod or option name.
       
   131 	 * @param array                $args    Optional. Setting arguments.
       
   132 	 *
       
   133 	 * @throws Exception If $id is not valid for this setting type.
       
   134 	 */
       
   135 	public function __construct( WP_Customize_Manager $manager, $id, array $args = array() ) {
       
   136 		if ( empty( $manager->nav_menus ) ) {
       
   137 			throw new Exception( 'Expected WP_Customize_Manager::$nav_menus to be set.' );
       
   138 		}
       
   139 
       
   140 		if ( ! preg_match( self::ID_PATTERN, $id, $matches ) ) {
       
   141 			throw new Exception( "Illegal widget setting ID: $id" );
       
   142 		}
       
   143 
       
   144 		$this->term_id = intval( $matches['id'] );
       
   145 
       
   146 		parent::__construct( $manager, $id, $args );
       
   147 	}
       
   148 
       
   149 	/**
       
   150 	 * Get the instance data for a given widget setting.
       
   151 	 *
       
   152 	 * @since 4.3.0
       
   153 	 *
       
   154 	 * @see wp_get_nav_menu_object()
       
   155 	 *
       
   156 	 * @return array Instance data.
       
   157 	 */
       
   158 	public function value() {
       
   159 		if ( $this->is_previewed && $this->_previewed_blog_id === get_current_blog_id() ) {
       
   160 			$undefined  = new stdClass(); // Symbol.
       
   161 			$post_value = $this->post_value( $undefined );
       
   162 
       
   163 			if ( $undefined === $post_value ) {
       
   164 				$value = $this->_original_value;
       
   165 			} else {
       
   166 				$value = $post_value;
       
   167 			}
       
   168 		} else {
       
   169 			$value = false;
       
   170 
       
   171 			// Note that a term_id of less than one indicates a nav_menu not yet inserted.
       
   172 			if ( $this->term_id > 0 ) {
       
   173 				$term = wp_get_nav_menu_object( $this->term_id );
       
   174 
       
   175 				if ( $term ) {
       
   176 					$value = wp_array_slice_assoc( (array) $term, array_keys( $this->default ) );
       
   177 
       
   178 					$nav_menu_options  = (array) get_option( 'nav_menu_options', array() );
       
   179 					$value['auto_add'] = false;
       
   180 
       
   181 					if ( isset( $nav_menu_options['auto_add'] ) && is_array( $nav_menu_options['auto_add'] ) ) {
       
   182 						$value['auto_add'] = in_array( $term->term_id, $nav_menu_options['auto_add'] );
       
   183 					}
       
   184 				}
       
   185 			}
       
   186 
       
   187 			if ( ! is_array( $value ) ) {
       
   188 				$value = $this->default;
       
   189 			}
       
   190 		}
       
   191 		return $value;
       
   192 	}
       
   193 
       
   194 	/**
       
   195 	 * Handle previewing the setting.
       
   196 	 *
       
   197 	 * @since 4.3.0
       
   198 	 * @since 4.4.0 Added boolean return value
       
   199 	 *
       
   200 	 * @see WP_Customize_Manager::post_value()
       
   201 	 *
       
   202 	 * @return bool False if method short-circuited due to no-op.
       
   203 	 */
       
   204 	public function preview() {
       
   205 		if ( $this->is_previewed ) {
       
   206 			return false;
       
   207 		}
       
   208 
       
   209 		$undefined = new stdClass();
       
   210 		$is_placeholder = ( $this->term_id < 0 );
       
   211 		$is_dirty = ( $undefined !== $this->post_value( $undefined ) );
       
   212 		if ( ! $is_placeholder && ! $is_dirty ) {
       
   213 			return false;
       
   214 		}
       
   215 
       
   216 		$this->is_previewed       = true;
       
   217 		$this->_original_value    = $this->value();
       
   218 		$this->_previewed_blog_id = get_current_blog_id();
       
   219 
       
   220 		add_filter( 'wp_get_nav_menus', array( $this, 'filter_wp_get_nav_menus' ), 10, 2 );
       
   221 		add_filter( 'wp_get_nav_menu_object', array( $this, 'filter_wp_get_nav_menu_object' ), 10, 2 );
       
   222 		add_filter( 'default_option_nav_menu_options', array( $this, 'filter_nav_menu_options' ) );
       
   223 		add_filter( 'option_nav_menu_options', array( $this, 'filter_nav_menu_options' ) );
       
   224 
       
   225 		return true;
       
   226 	}
       
   227 
       
   228 	/**
       
   229 	 * Filters the wp_get_nav_menus() result to ensure the inserted menu object is included, and the deleted one is removed.
       
   230 	 *
       
   231 	 * @since 4.3.0
       
   232 	 *
       
   233 	 * @see wp_get_nav_menus()
       
   234 	 *
       
   235 	 * @param array $menus An array of menu objects.
       
   236 	 * @param array $args  An array of arguments used to retrieve menu objects.
       
   237 	 * @return array
       
   238 	 */
       
   239 	public function filter_wp_get_nav_menus( $menus, $args ) {
       
   240 		if ( get_current_blog_id() !== $this->_previewed_blog_id ) {
       
   241 			return $menus;
       
   242 		}
       
   243 
       
   244 		$setting_value = $this->value();
       
   245 		$is_delete = ( false === $setting_value );
       
   246 		$index = -1;
       
   247 
       
   248 		// Find the existing menu item's position in the list.
       
   249 		foreach ( $menus as $i => $menu ) {
       
   250 			if ( (int) $this->term_id === (int) $menu->term_id || (int) $this->previous_term_id === (int) $menu->term_id ) {
       
   251 				$index = $i;
       
   252 				break;
       
   253 			}
       
   254 		}
       
   255 
       
   256 		if ( $is_delete ) {
       
   257 			// Handle deleted menu by removing it from the list.
       
   258 			if ( -1 !== $index ) {
       
   259 				array_splice( $menus, $index, 1 );
       
   260 			}
       
   261 		} else {
       
   262 			// Handle menus being updated or inserted.
       
   263 			$menu_obj = (object) array_merge( array(
       
   264 				'term_id'          => $this->term_id,
       
   265 				'term_taxonomy_id' => $this->term_id,
       
   266 				'slug'             => sanitize_title( $setting_value['name'] ),
       
   267 				'count'            => 0,
       
   268 				'term_group'       => 0,
       
   269 				'taxonomy'         => self::TAXONOMY,
       
   270 				'filter'           => 'raw',
       
   271 			), $setting_value );
       
   272 
       
   273 			array_splice( $menus, $index, ( -1 === $index ? 0 : 1 ), array( $menu_obj ) );
       
   274 		}
       
   275 
       
   276 		// Make sure the menu objects get re-sorted after an update/insert.
       
   277 		if ( ! $is_delete && ! empty( $args['orderby'] ) ) {
       
   278 			$menus = wp_list_sort( $menus, array(
       
   279 				$args['orderby'] => 'ASC',
       
   280 			) );
       
   281 		}
       
   282 		// @todo add support for $args['hide_empty'] === true
       
   283 
       
   284 		return $menus;
       
   285 	}
       
   286 
       
   287 	/**
       
   288 	 * Temporary non-closure passing of orderby value to function.
       
   289 	 *
       
   290 	 * @since 4.3.0
       
   291 	 * @var string
       
   292 	 *
       
   293 	 * @see WP_Customize_Nav_Menu_Setting::filter_wp_get_nav_menus()
       
   294 	 * @see WP_Customize_Nav_Menu_Setting::_sort_menus_by_orderby()
       
   295 	 */
       
   296 	protected $_current_menus_sort_orderby;
       
   297 
       
   298 	/**
       
   299 	 * Sort menu objects by the class-supplied orderby property.
       
   300 	 *
       
   301 	 * This is a workaround for a lack of closures.
       
   302 	 *
       
   303 	 * @since 4.3.0
       
   304 	 * @deprecated 4.7.0 Use wp_list_sort()
       
   305 	 *
       
   306 	 * @param object $menu1
       
   307 	 * @param object $menu2
       
   308 	 * @return int
       
   309 	 *
       
   310 	 * @see WP_Customize_Nav_Menu_Setting::filter_wp_get_nav_menus()
       
   311 	 */
       
   312 	protected function _sort_menus_by_orderby( $menu1, $menu2 ) {
       
   313 		_deprecated_function( __METHOD__, '4.7.0', 'wp_list_sort' );
       
   314 
       
   315 		$key = $this->_current_menus_sort_orderby;
       
   316 		return strcmp( $menu1->$key, $menu2->$key );
       
   317 	}
       
   318 
       
   319 	/**
       
   320 	 * Filters the wp_get_nav_menu_object() result to supply the previewed menu object.
       
   321 	 *
       
   322 	 * Requesting a nav_menu object by anything but ID is not supported.
       
   323 	 *
       
   324 	 * @since 4.3.0
       
   325 	 *
       
   326 	 * @see wp_get_nav_menu_object()
       
   327 	 *
       
   328 	 * @param object|null $menu_obj Object returned by wp_get_nav_menu_object().
       
   329 	 * @param string      $menu_id  ID of the nav_menu term. Requests by slug or name will be ignored.
       
   330 	 * @return object|null
       
   331 	 */
       
   332 	public function filter_wp_get_nav_menu_object( $menu_obj, $menu_id ) {
       
   333 		$ok = (
       
   334 			get_current_blog_id() === $this->_previewed_blog_id
       
   335 			&&
       
   336 			is_int( $menu_id )
       
   337 			&&
       
   338 			$menu_id === $this->term_id
       
   339 		);
       
   340 		if ( ! $ok ) {
       
   341 			return $menu_obj;
       
   342 		}
       
   343 
       
   344 		$setting_value = $this->value();
       
   345 
       
   346 		// Handle deleted menus.
       
   347 		if ( false === $setting_value ) {
       
   348 			return false;
       
   349 		}
       
   350 
       
   351 		// Handle sanitization failure by preventing short-circuiting.
       
   352 		if ( null === $setting_value ) {
       
   353 			return $menu_obj;
       
   354 		}
       
   355 
       
   356 		$menu_obj = (object) array_merge( array(
       
   357 				'term_id'          => $this->term_id,
       
   358 				'term_taxonomy_id' => $this->term_id,
       
   359 				'slug'             => sanitize_title( $setting_value['name'] ),
       
   360 				'count'            => 0,
       
   361 				'term_group'       => 0,
       
   362 				'taxonomy'         => self::TAXONOMY,
       
   363 				'filter'           => 'raw',
       
   364 			), $setting_value );
       
   365 
       
   366 		return $menu_obj;
       
   367 	}
       
   368 
       
   369 	/**
       
   370 	 * Filters the nav_menu_options option to include this menu's auto_add preference.
       
   371 	 *
       
   372 	 * @since 4.3.0
       
   373 	 *
       
   374 	 * @param array $nav_menu_options Nav menu options including auto_add.
       
   375 	 * @return array (Kaybe) modified nav menu options.
       
   376 	 */
       
   377 	public function filter_nav_menu_options( $nav_menu_options ) {
       
   378 		if ( $this->_previewed_blog_id !== get_current_blog_id() ) {
       
   379 			return $nav_menu_options;
       
   380 		}
       
   381 
       
   382 		$menu = $this->value();
       
   383 		$nav_menu_options = $this->filter_nav_menu_options_value(
       
   384 			$nav_menu_options,
       
   385 			$this->term_id,
       
   386 			false === $menu ? false : $menu['auto_add']
       
   387 		);
       
   388 
       
   389 		return $nav_menu_options;
       
   390 	}
       
   391 
       
   392 	/**
       
   393 	 * Sanitize an input.
       
   394 	 *
       
   395 	 * Note that parent::sanitize() erroneously does wp_unslash() on $value, but
       
   396 	 * we remove that in this override.
       
   397 	 *
       
   398 	 * @since 4.3.0
       
   399 	 *
       
   400 	 * @param array $value The value to sanitize.
       
   401 	 * @return array|false|null Null if an input isn't valid. False if it is marked for deletion.
       
   402 	 *                          Otherwise the sanitized value.
       
   403 	 */
       
   404 	public function sanitize( $value ) {
       
   405 		// Menu is marked for deletion.
       
   406 		if ( false === $value ) {
       
   407 			return $value;
       
   408 		}
       
   409 
       
   410 		// Invalid.
       
   411 		if ( ! is_array( $value ) ) {
       
   412 			return null;
       
   413 		}
       
   414 
       
   415 		$default = array(
       
   416 			'name'        => '',
       
   417 			'description' => '',
       
   418 			'parent'      => 0,
       
   419 			'auto_add'    => false,
       
   420 		);
       
   421 		$value = array_merge( $default, $value );
       
   422 		$value = wp_array_slice_assoc( $value, array_keys( $default ) );
       
   423 
       
   424 		$value['name']        = trim( esc_html( $value['name'] ) ); // This sanitization code is used in wp-admin/nav-menus.php.
       
   425 		$value['description'] = sanitize_text_field( $value['description'] );
       
   426 		$value['parent']      = max( 0, intval( $value['parent'] ) );
       
   427 		$value['auto_add']    = ! empty( $value['auto_add'] );
       
   428 
       
   429 		if ( '' === $value['name'] ) {
       
   430 			$value['name'] = _x( '(unnamed)', 'Missing menu name.' );
       
   431 		}
       
   432 
       
   433 		/** This filter is documented in wp-includes/class-wp-customize-setting.php */
       
   434 		return apply_filters( "customize_sanitize_{$this->id}", $value, $this );
       
   435 	}
       
   436 
       
   437 	/**
       
   438 	 * Storage for data to be sent back to client in customize_save_response filter.
       
   439 	 *
       
   440 	 * See {@see 'customize_save_response'}.
       
   441 	 *
       
   442 	 * @since 4.3.0
       
   443 	 * @var array
       
   444 	 *
       
   445 	 * @see WP_Customize_Nav_Menu_Setting::amend_customize_save_response()
       
   446 	 */
       
   447 	protected $_widget_nav_menu_updates = array();
       
   448 
       
   449 	/**
       
   450 	 * Create/update the nav_menu term for this setting.
       
   451 	 *
       
   452 	 * Any created menus will have their assigned term IDs exported to the client
       
   453 	 * via the {@see 'customize_save_response'} filter. Likewise, any errors will be exported
       
   454 	 * to the client via the customize_save_response() filter.
       
   455 	 *
       
   456 	 * To delete a menu, the client can send false as the value.
       
   457 	 *
       
   458 	 * @since 4.3.0
       
   459 	 *
       
   460 	 * @see wp_update_nav_menu_object()
       
   461 	 *
       
   462 	 * @param array|false $value {
       
   463 	 *     The value to update. Note that slug cannot be updated via wp_update_nav_menu_object().
       
   464 	 *     If false, then the menu will be deleted entirely.
       
   465 	 *
       
   466 	 *     @type string $name        The name of the menu to save.
       
   467 	 *     @type string $description The term description. Default empty string.
       
   468 	 *     @type int    $parent      The id of the parent term. Default 0.
       
   469 	 *     @type bool   $auto_add    Whether pages will auto_add to this menu. Default false.
       
   470 	 * }
       
   471 	 * @return null|void
       
   472 	 */
       
   473 	protected function update( $value ) {
       
   474 		if ( $this->is_updated ) {
       
   475 			return;
       
   476 		}
       
   477 
       
   478 		$this->is_updated = true;
       
   479 		$is_placeholder   = ( $this->term_id < 0 );
       
   480 		$is_delete        = ( false === $value );
       
   481 
       
   482 		add_filter( 'customize_save_response', array( $this, 'amend_customize_save_response' ) );
       
   483 
       
   484 		$auto_add = null;
       
   485 		if ( $is_delete ) {
       
   486 			// If the current setting term is a placeholder, a delete request is a no-op.
       
   487 			if ( $is_placeholder ) {
       
   488 				$this->update_status = 'deleted';
       
   489 			} else {
       
   490 				$r = wp_delete_nav_menu( $this->term_id );
       
   491 
       
   492 				if ( is_wp_error( $r ) ) {
       
   493 					$this->update_status = 'error';
       
   494 					$this->update_error  = $r;
       
   495 				} else {
       
   496 					$this->update_status = 'deleted';
       
   497 					$auto_add = false;
       
   498 				}
       
   499 			}
       
   500 		} else {
       
   501 			// Insert or update menu.
       
   502 			$menu_data = wp_array_slice_assoc( $value, array( 'description', 'parent' ) );
       
   503 			$menu_data['menu-name'] = $value['name'];
       
   504 
       
   505 			$menu_id = $is_placeholder ? 0 : $this->term_id;
       
   506 			$r = wp_update_nav_menu_object( $menu_id, wp_slash( $menu_data ) );
       
   507 			$original_name = $menu_data['menu-name'];
       
   508 			$name_conflict_suffix = 1;
       
   509 			while ( is_wp_error( $r ) && 'menu_exists' === $r->get_error_code() ) {
       
   510 				$name_conflict_suffix += 1;
       
   511 				/* translators: 1: original menu name, 2: duplicate count */
       
   512 				$menu_data['menu-name'] = sprintf( __( '%1$s (%2$d)' ), $original_name, $name_conflict_suffix );
       
   513 				$r = wp_update_nav_menu_object( $menu_id, wp_slash( $menu_data ) );
       
   514 			}
       
   515 
       
   516 			if ( is_wp_error( $r ) ) {
       
   517 				$this->update_status = 'error';
       
   518 				$this->update_error  = $r;
       
   519 			} else {
       
   520 				if ( $is_placeholder ) {
       
   521 					$this->previous_term_id = $this->term_id;
       
   522 					$this->term_id          = $r;
       
   523 					$this->update_status    = 'inserted';
       
   524 				} else {
       
   525 					$this->update_status = 'updated';
       
   526 				}
       
   527 
       
   528 				$auto_add = $value['auto_add'];
       
   529 			}
       
   530 		}
       
   531 
       
   532 		if ( null !== $auto_add ) {
       
   533 			$nav_menu_options = $this->filter_nav_menu_options_value(
       
   534 				(array) get_option( 'nav_menu_options', array() ),
       
   535 				$this->term_id,
       
   536 				$auto_add
       
   537 			);
       
   538 			update_option( 'nav_menu_options', $nav_menu_options );
       
   539 		}
       
   540 
       
   541 		if ( 'inserted' === $this->update_status ) {
       
   542 			// Make sure that new menus assigned to nav menu locations use their new IDs.
       
   543 			foreach ( $this->manager->settings() as $setting ) {
       
   544 				if ( ! preg_match( '/^nav_menu_locations\[/', $setting->id ) ) {
       
   545 					continue;
       
   546 				}
       
   547 
       
   548 				$post_value = $setting->post_value( null );
       
   549 				if ( ! is_null( $post_value ) && $this->previous_term_id === intval( $post_value ) ) {
       
   550 					$this->manager->set_post_value( $setting->id, $this->term_id );
       
   551 					$setting->save();
       
   552 				}
       
   553 			}
       
   554 
       
   555 			// Make sure that any nav_menu widgets referencing the placeholder nav menu get updated and sent back to client.
       
   556 			foreach ( array_keys( $this->manager->unsanitized_post_values() ) as $setting_id ) {
       
   557 				$nav_menu_widget_setting = $this->manager->get_setting( $setting_id );
       
   558 				if ( ! $nav_menu_widget_setting || ! preg_match( '/^widget_nav_menu\[/', $nav_menu_widget_setting->id ) ) {
       
   559 					continue;
       
   560 				}
       
   561 
       
   562 				$widget_instance = $nav_menu_widget_setting->post_value(); // Note that this calls WP_Customize_Widgets::sanitize_widget_instance().
       
   563 				if ( empty( $widget_instance['nav_menu'] ) || intval( $widget_instance['nav_menu'] ) !== $this->previous_term_id ) {
       
   564 					continue;
       
   565 				}
       
   566 
       
   567 				$widget_instance['nav_menu'] = $this->term_id;
       
   568 				$updated_widget_instance = $this->manager->widgets->sanitize_widget_js_instance( $widget_instance );
       
   569 				$this->manager->set_post_value( $nav_menu_widget_setting->id, $updated_widget_instance );
       
   570 				$nav_menu_widget_setting->save();
       
   571 
       
   572 				$this->_widget_nav_menu_updates[ $nav_menu_widget_setting->id ] = $updated_widget_instance;
       
   573 			}
       
   574 		}
       
   575 	}
       
   576 
       
   577 	/**
       
   578 	 * Updates a nav_menu_options array.
       
   579 	 *
       
   580 	 * @since 4.3.0
       
   581 	 *
       
   582 	 * @see WP_Customize_Nav_Menu_Setting::filter_nav_menu_options()
       
   583 	 * @see WP_Customize_Nav_Menu_Setting::update()
       
   584 	 *
       
   585 	 * @param array $nav_menu_options Array as returned by get_option( 'nav_menu_options' ).
       
   586 	 * @param int   $menu_id          The term ID for the given menu.
       
   587 	 * @param bool  $auto_add         Whether to auto-add or not.
       
   588 	 * @return array (Maybe) modified nav_menu_otions array.
       
   589 	 */
       
   590 	protected function filter_nav_menu_options_value( $nav_menu_options, $menu_id, $auto_add ) {
       
   591 		$nav_menu_options = (array) $nav_menu_options;
       
   592 		if ( ! isset( $nav_menu_options['auto_add'] ) ) {
       
   593 			$nav_menu_options['auto_add'] = array();
       
   594 		}
       
   595 
       
   596 		$i = array_search( $menu_id, $nav_menu_options['auto_add'] );
       
   597 		if ( $auto_add && false === $i ) {
       
   598 			array_push( $nav_menu_options['auto_add'], $this->term_id );
       
   599 		} elseif ( ! $auto_add && false !== $i ) {
       
   600 			array_splice( $nav_menu_options['auto_add'], $i, 1 );
       
   601 		}
       
   602 
       
   603 		return $nav_menu_options;
       
   604 	}
       
   605 
       
   606 	/**
       
   607 	 * Export data for the JS client.
       
   608 	 *
       
   609 	 * @since 4.3.0
       
   610 	 *
       
   611 	 * @see WP_Customize_Nav_Menu_Setting::update()
       
   612 	 *
       
   613 	 * @param array $data Additional information passed back to the 'saved' event on `wp.customize`.
       
   614 	 * @return array Export data.
       
   615 	 */
       
   616 	public function amend_customize_save_response( $data ) {
       
   617 		if ( ! isset( $data['nav_menu_updates'] ) ) {
       
   618 			$data['nav_menu_updates'] = array();
       
   619 		}
       
   620 		if ( ! isset( $data['widget_nav_menu_updates'] ) ) {
       
   621 			$data['widget_nav_menu_updates'] = array();
       
   622 		}
       
   623 
       
   624 		$data['nav_menu_updates'][] = array(
       
   625 			'term_id'          => $this->term_id,
       
   626 			'previous_term_id' => $this->previous_term_id,
       
   627 			'error'            => $this->update_error ? $this->update_error->get_error_code() : null,
       
   628 			'status'           => $this->update_status,
       
   629 			'saved_value'      => 'deleted' === $this->update_status ? null : $this->value(),
       
   630 		);
       
   631 
       
   632 		$data['widget_nav_menu_updates'] = array_merge(
       
   633 			$data['widget_nav_menu_updates'],
       
   634 			$this->_widget_nav_menu_updates
       
   635 		);
       
   636 		$this->_widget_nav_menu_updates = array();
       
   637 
       
   638 		return $data;
       
   639 	}
       
   640 }