WP_Customize_Nav_Menu_Item_Setting

Customize Setting to represent a nav_menu.

Defined (1)

The class is defined in the following location(s).

/wp-includes/customize/class-wp-customize-nav-menu-item-setting.php  
  1. class WP_Customize_Nav_Menu_Item_Setting extends WP_Customize_Setting { 
  2.  
  3. const ID_PATTERN = '/^nav_menu_item\[(?P<id>-?\d+)\]$/'; 
  4.  
  5. const POST_TYPE = 'nav_menu_item'; 
  6.  
  7. const TYPE = 'nav_menu_item'; 
  8.  
  9. /** 
  10. * Setting type. 
  11. * @since 4.3.0 
  12. * @access public 
  13. * @var string 
  14. */ 
  15. public $type = self::TYPE; 
  16.  
  17. /** 
  18. * Default setting value. 
  19. * @since 4.3.0 
  20. * @access public 
  21. * @var array 
  22. * @see wp_setup_nav_menu_item() 
  23. */ 
  24. public $default = array( 
  25. // The $menu_item_data for wp_update_nav_menu_item(). 
  26. 'object_id' => 0,  
  27. 'object' => '', // Taxonomy name. 
  28. 'menu_item_parent' => 0, // A.K.A. menu-item-parent-id; note that post_parent is different, and not included. 
  29. 'position' => 0, // A.K.A. menu_order. 
  30. 'type' => 'custom', // Note that type_label is not included here. 
  31. 'title' => '',  
  32. 'url' => '',  
  33. 'target' => '',  
  34. 'attr_title' => '',  
  35. 'description' => '',  
  36. 'classes' => '',  
  37. 'xfn' => '',  
  38. 'status' => 'publish',  
  39. 'original_title' => '',  
  40. 'nav_menu_term_id' => 0, // This will be supplied as the $menu_id arg for wp_update_nav_menu_item(). 
  41. '_invalid' => false,  
  42. ); 
  43.  
  44. /** 
  45. * Default transport. 
  46. * @since 4.3.0 
  47. * @since 4.5.0 Default changed to 'refresh' 
  48. * @access public 
  49. * @var string 
  50. */ 
  51. public $transport = 'refresh'; 
  52.  
  53. /** 
  54. * The post ID represented by this setting instance. This is the db_id. 
  55. * A negative value represents a placeholder ID for a new menu not yet saved. 
  56. * @since 4.3.0 
  57. * @access public 
  58. * @var int 
  59. */ 
  60. public $post_id; 
  61.  
  62. /** 
  63. * Storage of pre-setup menu item to prevent wasted calls to wp_setup_nav_menu_item(). 
  64. * @since 4.3.0 
  65. * @access protected 
  66. * @var array 
  67. */ 
  68. protected $value; 
  69.  
  70. /** 
  71. * Previous (placeholder) post ID used before creating a new menu item. 
  72. * This value will be exported to JS via the customize_save_response filter 
  73. * so that JavaScript can update the settings to refer to the newly-assigned 
  74. * post ID. This value is always negative to indicate it does not refer to 
  75. * a real post. 
  76. * @since 4.3.0 
  77. * @access public 
  78. * @var int 
  79. * @see WP_Customize_Nav_Menu_Item_Setting::update() 
  80. * @see WP_Customize_Nav_Menu_Item_Setting::amend_customize_save_response() 
  81. */ 
  82. public $previous_post_id; 
  83.  
  84. /** 
  85. * When previewing or updating a menu item, this stores the previous nav_menu_term_id 
  86. * which ensures that we can apply the proper filters. 
  87. * @since 4.3.0 
  88. * @access public 
  89. * @var int 
  90. */ 
  91. public $original_nav_menu_term_id; 
  92.  
  93. /** 
  94. * Whether or not update() was called. 
  95. * @since 4.3.0 
  96. * @access protected 
  97. * @var bool 
  98. */ 
  99. protected $is_updated = false; 
  100.  
  101. /** 
  102. * Status for calling the update method, used in customize_save_response filter. 
  103. * When status is inserted, the placeholder post ID is stored in $previous_post_id. 
  104. * When status is error, the error is stored in $update_error. 
  105. * @since 4.3.0 
  106. * @access public 
  107. * @var string updated|inserted|deleted|error 
  108. * @see WP_Customize_Nav_Menu_Item_Setting::update() 
  109. * @see WP_Customize_Nav_Menu_Item_Setting::amend_customize_save_response() 
  110. */ 
  111. public $update_status; 
  112.  
  113. /** 
  114. * Any error object returned by wp_update_nav_menu_item() when setting is updated. 
  115. * @since 4.3.0 
  116. * @access public 
  117. * @var WP_Error 
  118. * @see WP_Customize_Nav_Menu_Item_Setting::update() 
  119. * @see WP_Customize_Nav_Menu_Item_Setting::amend_customize_save_response() 
  120. */ 
  121. public $update_error; 
  122.  
  123. /** 
  124. * Constructor. 
  125. * Any supplied $args override class property defaults. 
  126. * @since 4.3.0 
  127. * @access public 
  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. * @throws Exception If $id is not valid for this setting type. 
  133. */ 
  134. public function __construct( WP_Customize_Manager $manager, $id, array $args = array() ) { 
  135. if ( empty( $manager->nav_menus ) ) { 
  136. throw new Exception( 'Expected WP_Customize_Manager::$nav_menus to be set.' ); 
  137.  
  138. if ( ! preg_match( self::ID_PATTERN, $id, $matches ) ) { 
  139. throw new Exception( "Illegal widget setting ID: $id" ); 
  140.  
  141. $this->post_id = intval( $matches['id'] ); 
  142. add_action( 'wp_update_nav_menu_item', array( $this, 'flush_cached_value' ), 10, 2 ); 
  143.  
  144. parent::__construct( $manager, $id, $args ); 
  145.  
  146. // Ensure that an initially-supplied value is valid. 
  147. if ( isset( $this->value ) ) { 
  148. $this->populate_value(); 
  149. foreach ( array_diff( array_keys( $this->default ), array_keys( $this->value ) ) as $missing ) { 
  150. throw new Exception( "Supplied nav_menu_item value missing property: $missing" ); 
  151.  
  152.  
  153. /** 
  154. * Clear the cached value when this nav menu item is updated. 
  155. * @since 4.3.0 
  156. * @access public 
  157. * @param int $menu_id The term ID for the menu. 
  158. * @param int $menu_item_id The post ID for the menu item. 
  159. */ 
  160. public function flush_cached_value( $menu_id, $menu_item_id ) { 
  161. unset( $menu_id ); 
  162. if ( $menu_item_id === $this->post_id ) { 
  163. $this->value = null; 
  164.  
  165. /** 
  166. * Get the instance data for a given nav_menu_item setting. 
  167. * @since 4.3.0 
  168. * @access public 
  169. * @see wp_setup_nav_menu_item() 
  170. * @return array|false Instance data array, or false if the item is marked for deletion. 
  171. */ 
  172. public function value() { 
  173. if ( $this->is_previewed && $this->_previewed_blog_id === get_current_blog_id() ) { 
  174. $undefined = new stdClass(); // Symbol. 
  175. $post_value = $this->post_value( $undefined ); 
  176.  
  177. if ( $undefined === $post_value ) { 
  178. $value = $this->_original_value; 
  179. } else { 
  180. $value = $post_value; 
  181. } else if ( isset( $this->value ) ) { 
  182. $value = $this->value; 
  183. } else { 
  184. $value = false; 
  185.  
  186. // Note that a ID of less than one indicates a nav_menu not yet inserted. 
  187. if ( $this->post_id > 0 ) { 
  188. $post = get_post( $this->post_id ); 
  189. if ( $post && self::POST_TYPE === $post->post_type ) { 
  190. $value = (array) wp_setup_nav_menu_item( $post ); 
  191.  
  192. if ( ! is_array( $value ) ) { 
  193. $value = $this->default; 
  194.  
  195. // Cache the value for future calls to avoid having to re-call wp_setup_nav_menu_item(). 
  196. $this->value = $value; 
  197. $this->populate_value(); 
  198. $value = $this->value; 
  199.  
  200. return $value; 
  201.  
  202. /** 
  203. * Ensure that the value is fully populated with the necessary properties. 
  204. * Translates some properties added by wp_setup_nav_menu_item() and removes others. 
  205. * @since 4.3.0 
  206. * @access protected 
  207. * @see WP_Customize_Nav_Menu_Item_Setting::value() 
  208. */ 
  209. protected function populate_value() { 
  210. if ( ! is_array( $this->value ) ) { 
  211. return; 
  212.  
  213. if ( isset( $this->value['menu_order'] ) ) { 
  214. $this->value['position'] = $this->value['menu_order']; 
  215. unset( $this->value['menu_order'] ); 
  216. if ( isset( $this->value['post_status'] ) ) { 
  217. $this->value['status'] = $this->value['post_status']; 
  218. unset( $this->value['post_status'] ); 
  219.  
  220. if ( ! isset( $this->value['original_title'] ) ) { 
  221. $original_title = ''; 
  222. if ( 'post_type' === $this->value['type'] ) { 
  223. $original_title = get_the_title( $this->value['object_id'] ); 
  224. } elseif ( 'taxonomy' === $this->value['type'] ) { 
  225. $original_title = get_term_field( 'name', $this->value['object_id'], $this->value['object'], 'raw' ); 
  226. if ( is_wp_error( $original_title ) ) { 
  227. $original_title = ''; 
  228. $this->value['original_title'] = html_entity_decode( $original_title, ENT_QUOTES, get_bloginfo( 'charset' ) ); 
  229.  
  230. if ( ! isset( $this->value['nav_menu_term_id'] ) && $this->post_id > 0 ) { 
  231. $menus = wp_get_post_terms( $this->post_id, WP_Customize_Nav_Menu_Setting::TAXONOMY, array( 
  232. 'fields' => 'ids',  
  233. ) ); 
  234. if ( ! empty( $menus ) ) { 
  235. $this->value['nav_menu_term_id'] = array_shift( $menus ); 
  236. } else { 
  237. $this->value['nav_menu_term_id'] = 0; 
  238.  
  239. foreach ( array( 'object_id', 'menu_item_parent', 'nav_menu_term_id' ) as $key ) { 
  240. if ( ! is_int( $this->value[ $key ] ) ) { 
  241. $this->value[ $key ] = intval( $this->value[ $key ] ); 
  242. foreach ( array( 'classes', 'xfn' ) as $key ) { 
  243. if ( is_array( $this->value[ $key ] ) ) { 
  244. $this->value[ $key ] = implode( ' ', $this->value[ $key ] ); 
  245.  
  246. if ( ! isset( $this->value['title'] ) ) { 
  247. $this->value['title'] = ''; 
  248.  
  249. if ( ! isset( $this->value['_invalid'] ) ) { 
  250. $this->value['_invalid'] = false; 
  251. $is_known_invalid = ( 
  252. ( ( 'post_type' === $this->value['type'] || 'post_type_archive' === $this->value['type'] ) && ! post_type_exists( $this->value['object'] ) ) 
  253. || 
  254. ( 'taxonomy' === $this->value['type'] && ! taxonomy_exists( $this->value['object'] ) ) 
  255. ); 
  256. if ( $is_known_invalid ) { 
  257. $this->value['_invalid'] = true; 
  258.  
  259. // Remove remaining properties available on a setup nav_menu_item post object which aren't relevant to the setting value. 
  260. $irrelevant_properties = array( 
  261. 'ID',  
  262. 'comment_count',  
  263. 'comment_status',  
  264. 'db_id',  
  265. 'filter',  
  266. 'guid',  
  267. 'ping_status',  
  268. 'pinged',  
  269. 'post_author',  
  270. 'post_content',  
  271. 'post_content_filtered',  
  272. 'post_date',  
  273. 'post_date_gmt',  
  274. 'post_excerpt',  
  275. 'post_mime_type',  
  276. 'post_modified',  
  277. 'post_modified_gmt',  
  278. 'post_name',  
  279. 'post_parent',  
  280. 'post_password',  
  281. 'post_title',  
  282. 'post_type',  
  283. 'to_ping',  
  284. ); 
  285. foreach ( $irrelevant_properties as $property ) { 
  286. unset( $this->value[ $property ] ); 
  287.  
  288. /** 
  289. * Handle previewing the setting. 
  290. * @since 4.3.0 
  291. * @since 4.4.0 Added boolean return value. 
  292. * @access public 
  293. * @see WP_Customize_Manager::post_value() 
  294. * @return bool False if method short-circuited due to no-op. 
  295. */ 
  296. public function preview() { 
  297. if ( $this->is_previewed ) { 
  298. return false; 
  299.  
  300. $undefined = new stdClass(); 
  301. $is_placeholder = ( $this->post_id < 0 ); 
  302. $is_dirty = ( $undefined !== $this->post_value( $undefined ) ); 
  303. if ( ! $is_placeholder && ! $is_dirty ) { 
  304. return false; 
  305.  
  306. $this->is_previewed = true; 
  307. $this->_original_value = $this->value(); 
  308. $this->original_nav_menu_term_id = $this->_original_value['nav_menu_term_id']; 
  309. $this->_previewed_blog_id = get_current_blog_id(); 
  310.  
  311. add_filter( 'wp_get_nav_menu_items', array( $this, 'filter_wp_get_nav_menu_items' ), 10, 3 ); 
  312.  
  313. $sort_callback = array( __CLASS__, 'sort_wp_get_nav_menu_items' ); 
  314. if ( ! has_filter( 'wp_get_nav_menu_items', $sort_callback ) ) { 
  315. add_filter( 'wp_get_nav_menu_items', array( __CLASS__, 'sort_wp_get_nav_menu_items' ), 1000, 3 ); 
  316.  
  317. // @todo Add get_post_metadata filters for plugins to add their data. 
  318.  
  319. return true; 
  320.  
  321. /** 
  322. * Filter the wp_get_nav_menu_items() result to supply the previewed menu items. 
  323. * @since 4.3.0 
  324. * @access public 
  325. * @see wp_get_nav_menu_items() 
  326. * @param array $items An array of menu item post objects. 
  327. * @param object $menu The menu object. 
  328. * @param array $args An array of arguments used to retrieve menu item objects. 
  329. * @return array Array of menu items,  
  330. */ 
  331. public function filter_wp_get_nav_menu_items( $items, $menu, $args ) { 
  332. $this_item = $this->value(); 
  333. $current_nav_menu_term_id = $this_item['nav_menu_term_id']; 
  334. unset( $this_item['nav_menu_term_id'] ); 
  335.  
  336. $should_filter = ( 
  337. $menu->term_id === $this->original_nav_menu_term_id 
  338. || 
  339. $menu->term_id === $current_nav_menu_term_id 
  340. ); 
  341. if ( ! $should_filter ) { 
  342. return $items; 
  343.  
  344. // Handle deleted menu item, or menu item moved to another menu. 
  345. $should_remove = ( 
  346. false === $this_item 
  347. || 
  348. true === $this_item['_invalid'] 
  349. || 
  350. $this->original_nav_menu_term_id === $menu->term_id 
  351. && 
  352. $current_nav_menu_term_id !== $this->original_nav_menu_term_id 
  353. ); 
  354. if ( $should_remove ) { 
  355. $filtered_items = array(); 
  356. foreach ( $items as $item ) { 
  357. if ( $item->db_id !== $this->post_id ) { 
  358. $filtered_items[] = $item; 
  359. return $filtered_items; 
  360.  
  361. $mutated = false; 
  362. $should_update = ( 
  363. is_array( $this_item ) 
  364. && 
  365. $current_nav_menu_term_id === $menu->term_id 
  366. ); 
  367. if ( $should_update ) { 
  368. foreach ( $items as $item ) { 
  369. if ( $item->db_id === $this->post_id ) { 
  370. foreach ( get_object_vars( $this->value_as_wp_post_nav_menu_item() ) as $key => $value ) { 
  371. $item->$key = $value; 
  372. $mutated = true; 
  373.  
  374. // Not found so we have to append it.. 
  375. if ( ! $mutated ) { 
  376. $items[] = $this->value_as_wp_post_nav_menu_item(); 
  377.  
  378. return $items; 
  379.  
  380. /** 
  381. * Re-apply the tail logic also applied on $items by wp_get_nav_menu_items(). 
  382. * @since 4.3.0 
  383. * @access public 
  384. * @static 
  385. * @see wp_get_nav_menu_items() 
  386. * @param array $items An array of menu item post objects. 
  387. * @param object $menu The menu object. 
  388. * @param array $args An array of arguments used to retrieve menu item objects. 
  389. * @return array Array of menu items,  
  390. */ 
  391. public static function sort_wp_get_nav_menu_items( $items, $menu, $args ) { 
  392. // @todo We should probably re-apply some constraints imposed by $args. 
  393. unset( $args['include'] ); 
  394.  
  395. // Remove invalid items only in front end. 
  396. if ( ! is_admin() ) { 
  397. $items = array_filter( $items, '_is_valid_nav_menu_item' ); 
  398.  
  399. if ( ARRAY_A === $args['output'] ) { 
  400. $GLOBALS['_menu_item_sort_prop'] = $args['output_key']; 
  401. usort( $items, '_sort_nav_menu_items' ); 
  402. $i = 1; 
  403.  
  404. foreach ( $items as $k => $item ) { 
  405. $items[ $k ]->{$args['output_key']} = $i++; 
  406.  
  407. return $items; 
  408.  
  409. /** 
  410. * Get the value emulated into a WP_Post and set up as a nav_menu_item. 
  411. * @since 4.3.0 
  412. * @access public 
  413. * @return WP_Post With wp_setup_nav_menu_item() applied. 
  414. */ 
  415. public function value_as_wp_post_nav_menu_item() { 
  416. $item = (object) $this->value(); 
  417. unset( $item->nav_menu_term_id ); 
  418.  
  419. $item->post_status = $item->status; 
  420. unset( $item->status ); 
  421.  
  422. $item->post_type = 'nav_menu_item'; 
  423. $item->menu_order = $item->position; 
  424. unset( $item->position ); 
  425.  
  426. if ( $item->title ) { 
  427. $item->post_title = $item->title; 
  428.  
  429. $item->ID = $this->post_id; 
  430. $item->db_id = $this->post_id; 
  431. $post = new WP_Post( (object) $item ); 
  432.  
  433. if ( empty( $post->post_author ) ) { 
  434. $post->post_author = get_current_user_id(); 
  435.  
  436. if ( ! isset( $post->type_label ) ) { 
  437. if ( 'post_type' === $post->type ) { 
  438. $object = get_post_type_object( $post->object ); 
  439. if ( $object ) { 
  440. $post->type_label = $object->labels->singular_name; 
  441. } else { 
  442. $post->type_label = $post->object; 
  443. } elseif ( 'taxonomy' == $post->type ) { 
  444. $object = get_taxonomy( $post->object ); 
  445. if ( $object ) { 
  446. $post->type_label = $object->labels->singular_name; 
  447. } else { 
  448. $post->type_label = $post->object; 
  449. } else { 
  450. $post->type_label = __( 'Custom Link' ); 
  451.  
  452. /** This filter is documented in wp-includes/nav-menu.php */ 
  453. $post->attr_title = apply_filters( 'nav_menu_attr_title', $post->attr_title ); 
  454.  
  455. /** This filter is documented in wp-includes/nav-menu.php */ 
  456. $post->description = apply_filters( 'nav_menu_description', wp_trim_words( $post->description, 200 ) ); 
  457.  
  458. return $post; 
  459.  
  460. /** 
  461. * Sanitize an input. 
  462. * Note that parent::sanitize() erroneously does wp_unslash() on $value, but 
  463. * we remove that in this override. 
  464. * @since 4.3.0 
  465. * @access public 
  466. * @param array $menu_item_value The value to sanitize. 
  467. * @return array|false|null Null if an input isn't valid. False if it is marked for deletion. 
  468. * Otherwise the sanitized value. 
  469. */ 
  470. public function sanitize( $menu_item_value ) { 
  471. // Menu is marked for deletion. 
  472. if ( false === $menu_item_value ) { 
  473. return $menu_item_value; 
  474.  
  475. // Invalid. 
  476. if ( ! is_array( $menu_item_value ) ) { 
  477. return null; 
  478.  
  479. $default = array( 
  480. 'object_id' => 0,  
  481. 'object' => '',  
  482. 'menu_item_parent' => 0,  
  483. 'position' => 0,  
  484. 'type' => 'custom',  
  485. 'title' => '',  
  486. 'url' => '',  
  487. 'target' => '',  
  488. 'attr_title' => '',  
  489. 'description' => '',  
  490. 'classes' => '',  
  491. 'xfn' => '',  
  492. 'status' => 'publish',  
  493. 'original_title' => '',  
  494. 'nav_menu_term_id' => 0,  
  495. '_invalid' => false,  
  496. ); 
  497. $menu_item_value = array_merge( $default, $menu_item_value ); 
  498. $menu_item_value = wp_array_slice_assoc( $menu_item_value, array_keys( $default ) ); 
  499. $menu_item_value['position'] = intval( $menu_item_value['position'] ); 
  500.  
  501. foreach ( array( 'object_id', 'menu_item_parent', 'nav_menu_term_id' ) as $key ) { 
  502. // Note we need to allow negative-integer IDs for previewed objects not inserted yet. 
  503. $menu_item_value[ $key ] = intval( $menu_item_value[ $key ] ); 
  504.  
  505. foreach ( array( 'type', 'object', 'target' ) as $key ) { 
  506. $menu_item_value[ $key ] = sanitize_key( $menu_item_value[ $key ] ); 
  507.  
  508. foreach ( array( 'xfn', 'classes' ) as $key ) { 
  509. $value = $menu_item_value[ $key ]; 
  510. if ( ! is_array( $value ) ) { 
  511. $value = explode( ' ', $value ); 
  512. $menu_item_value[ $key ] = implode( ' ', array_map( 'sanitize_html_class', $value ) ); 
  513.  
  514. $menu_item_value['original_title'] = sanitize_text_field( $menu_item_value['original_title'] ); 
  515.  
  516. // Apply the same filters as when calling wp_insert_post(). 
  517. $menu_item_value['title'] = wp_unslash( apply_filters( 'title_save_pre', wp_slash( $menu_item_value['title'] ) ) ); 
  518. $menu_item_value['attr_title'] = wp_unslash( apply_filters( 'excerpt_save_pre', wp_slash( $menu_item_value['attr_title'] ) ) ); 
  519. $menu_item_value['description'] = wp_unslash( apply_filters( 'content_save_pre', wp_slash( $menu_item_value['description'] ) ) ); 
  520.  
  521. $menu_item_value['url'] = esc_url_raw( $menu_item_value['url'] ); 
  522. if ( 'publish' !== $menu_item_value['status'] ) { 
  523. $menu_item_value['status'] = 'draft'; 
  524.  
  525. $menu_item_value['_invalid'] = (bool) $menu_item_value['_invalid']; 
  526.  
  527. /** This filter is documented in wp-includes/class-wp-customize-setting.php */ 
  528. return apply_filters( "customize_sanitize_{$this->id}", $menu_item_value, $this ); 
  529.  
  530. /** 
  531. * Create/update the nav_menu_item post for this setting. 
  532. * Any created menu items will have their assigned post IDs exported to the client 
  533. * via the customize_save_response filter. Likewise, any errors will be exported 
  534. * to the client via the customize_save_response() filter. 
  535. * To delete a menu, the client can send false as the value. 
  536. * @since 4.3.0 
  537. * @access protected 
  538. * @see wp_update_nav_menu_item() 
  539. * @param array|false $value The menu item array to update. If false, then the menu item will be deleted 
  540. * entirely. See WP_Customize_Nav_Menu_Item_Setting::$default for what the value 
  541. * should consist of. 
  542. * @return null|void 
  543. */ 
  544. protected function update( $value ) { 
  545. if ( $this->is_updated ) { 
  546. return; 
  547.  
  548. $this->is_updated = true; 
  549. $is_placeholder = ( $this->post_id < 0 ); 
  550. $is_delete = ( false === $value ); 
  551.  
  552. // Update the cached value. 
  553. $this->value = $value; 
  554.  
  555. add_filter( 'customize_save_response', array( $this, 'amend_customize_save_response' ) ); 
  556.  
  557. if ( $is_delete ) { 
  558. // If the current setting post is a placeholder, a delete request is a no-op. 
  559. if ( $is_placeholder ) { 
  560. $this->update_status = 'deleted'; 
  561. } else { 
  562. $r = wp_delete_post( $this->post_id, true ); 
  563.  
  564. if ( false === $r ) { 
  565. $this->update_error = new WP_Error( 'delete_failure' ); 
  566. $this->update_status = 'error'; 
  567. } else { 
  568. $this->update_status = 'deleted'; 
  569. // @todo send back the IDs for all associated nav menu items deleted, so these settings (and controls) can be removed from Customizer? 
  570. } else { 
  571.  
  572. // Handle saving menu items for menus that are being newly-created. 
  573. if ( $value['nav_menu_term_id'] < 0 ) { 
  574. $nav_menu_setting_id = sprintf( 'nav_menu[%s]', $value['nav_menu_term_id'] ); 
  575. $nav_menu_setting = $this->manager->get_setting( $nav_menu_setting_id ); 
  576.  
  577. if ( ! $nav_menu_setting || ! ( $nav_menu_setting instanceof WP_Customize_Nav_Menu_Setting ) ) { 
  578. $this->update_status = 'error'; 
  579. $this->update_error = new WP_Error( 'unexpected_nav_menu_setting' ); 
  580. return; 
  581.  
  582. if ( false === $nav_menu_setting->save() ) { 
  583. $this->update_status = 'error'; 
  584. $this->update_error = new WP_Error( 'nav_menu_setting_failure' ); 
  585. return; 
  586.  
  587. if ( $nav_menu_setting->previous_term_id !== intval( $value['nav_menu_term_id'] ) ) { 
  588. $this->update_status = 'error'; 
  589. $this->update_error = new WP_Error( 'unexpected_previous_term_id' ); 
  590. return; 
  591.  
  592. $value['nav_menu_term_id'] = $nav_menu_setting->term_id; 
  593.  
  594. // Handle saving a nav menu item that is a child of a nav menu item being newly-created. 
  595. if ( $value['menu_item_parent'] < 0 ) { 
  596. $parent_nav_menu_item_setting_id = sprintf( 'nav_menu_item[%s]', $value['menu_item_parent'] ); 
  597. $parent_nav_menu_item_setting = $this->manager->get_setting( $parent_nav_menu_item_setting_id ); 
  598.  
  599. if ( ! $parent_nav_menu_item_setting || ! ( $parent_nav_menu_item_setting instanceof WP_Customize_Nav_Menu_Item_Setting ) ) { 
  600. $this->update_status = 'error'; 
  601. $this->update_error = new WP_Error( 'unexpected_nav_menu_item_setting' ); 
  602. return; 
  603.  
  604. if ( false === $parent_nav_menu_item_setting->save() ) { 
  605. $this->update_status = 'error'; 
  606. $this->update_error = new WP_Error( 'nav_menu_item_setting_failure' ); 
  607. return; 
  608.  
  609. if ( $parent_nav_menu_item_setting->previous_post_id !== intval( $value['menu_item_parent'] ) ) { 
  610. $this->update_status = 'error'; 
  611. $this->update_error = new WP_Error( 'unexpected_previous_post_id' ); 
  612. return; 
  613.  
  614. $value['menu_item_parent'] = $parent_nav_menu_item_setting->post_id; 
  615.  
  616. // Insert or update menu. 
  617. $menu_item_data = array( 
  618. 'menu-item-object-id' => $value['object_id'],  
  619. 'menu-item-object' => $value['object'],  
  620. 'menu-item-parent-id' => $value['menu_item_parent'],  
  621. 'menu-item-position' => $value['position'],  
  622. 'menu-item-type' => $value['type'],  
  623. 'menu-item-title' => $value['title'],  
  624. 'menu-item-url' => $value['url'],  
  625. 'menu-item-description' => $value['description'],  
  626. 'menu-item-attr-title' => $value['attr_title'],  
  627. 'menu-item-target' => $value['target'],  
  628. 'menu-item-classes' => $value['classes'],  
  629. 'menu-item-xfn' => $value['xfn'],  
  630. 'menu-item-status' => $value['status'],  
  631. ); 
  632.  
  633. $r = wp_update_nav_menu_item( 
  634. $value['nav_menu_term_id'],  
  635. $is_placeholder ? 0 : $this->post_id,  
  636. wp_slash( $menu_item_data ) 
  637. ); 
  638.  
  639. if ( is_wp_error( $r ) ) { 
  640. $this->update_status = 'error'; 
  641. $this->update_error = $r; 
  642. } else { 
  643. if ( $is_placeholder ) { 
  644. $this->previous_post_id = $this->post_id; 
  645. $this->post_id = $r; 
  646. $this->update_status = 'inserted'; 
  647. } else { 
  648. $this->update_status = 'updated'; 
  649.  
  650.  
  651. /** 
  652. * Export data for the JS client. 
  653. * @since 4.3.0 
  654. * @access public 
  655. * @see WP_Customize_Nav_Menu_Item_Setting::update() 
  656. * @param array $data Additional information passed back to the 'saved' event on `wp.customize`. 
  657. * @return array Save response data. 
  658. */ 
  659. public function amend_customize_save_response( $data ) { 
  660. if ( ! isset( $data['nav_menu_item_updates'] ) ) { 
  661. $data['nav_menu_item_updates'] = array(); 
  662.  
  663. $data['nav_menu_item_updates'][] = array( 
  664. 'post_id' => $this->post_id,  
  665. 'previous_post_id' => $this->previous_post_id,  
  666. 'error' => $this->update_error ? $this->update_error->get_error_code() : null,  
  667. 'status' => $this->update_status,  
  668. ); 
  669. return $data;