[ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
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 * @var string 33 */ 34 public $type = self::TYPE; 35 36 /** 37 * Default setting value. 38 * 39 * @since 4.3.0 40 * @var array 41 * 42 * @see wp_setup_nav_menu_item() 43 */ 44 public $default = array( 45 // The $menu_item_data for wp_update_nav_menu_item(). 46 'object_id' => 0, 47 'object' => '', // Taxonomy name. 48 'menu_item_parent' => 0, // A.K.A. menu-item-parent-id; note that post_parent is different, and not included. 49 'position' => 0, // A.K.A. menu_order. 50 'type' => 'custom', // Note that type_label is not included here. 51 'title' => '', 52 'url' => '', 53 'target' => '', 54 'attr_title' => '', 55 'description' => '', 56 'classes' => '', 57 'xfn' => '', 58 'status' => 'publish', 59 'nav_menu_term_id' => 0, // This will be supplied as the $menu_id arg for wp_update_nav_menu_item(). 60 '_invalid' => false, 61 ); 62 63 /** 64 * Default transport. 65 * 66 * @since 4.3.0 67 * @since 4.5.0 Default changed to 'refresh' 68 * @var string 69 */ 70 public $transport = 'refresh'; 71 72 /** 73 * The post ID represented by this setting instance. This is the db_id. 74 * 75 * A negative value represents a placeholder ID for a new menu not yet saved. 76 * 77 * @since 4.3.0 78 * @var int 79 */ 80 public $post_id; 81 82 /** 83 * Storage of pre-setup menu item to prevent wasted calls to wp_setup_nav_menu_item(). 84 * 85 * @since 4.3.0 86 * @var array|null 87 */ 88 protected $value; 89 90 /** 91 * Previous (placeholder) post ID used before creating a new menu item. 92 * 93 * This value will be exported to JS via the customize_save_response filter 94 * so that JavaScript can update the settings to refer to the newly-assigned 95 * post ID. This value is always negative to indicate it does not refer to 96 * a real post. 97 * 98 * @since 4.3.0 99 * @var int 100 * 101 * @see WP_Customize_Nav_Menu_Item_Setting::update() 102 * @see WP_Customize_Nav_Menu_Item_Setting::amend_customize_save_response() 103 */ 104 public $previous_post_id; 105 106 /** 107 * When previewing or updating a menu item, this stores the previous nav_menu_term_id 108 * which ensures that we can apply the proper filters. 109 * 110 * @since 4.3.0 111 * @var int 112 */ 113 public $original_nav_menu_term_id; 114 115 /** 116 * Whether or not update() was called. 117 * 118 * @since 4.3.0 119 * @var bool 120 */ 121 protected $is_updated = false; 122 123 /** 124 * Status for calling the update method, used in customize_save_response filter. 125 * 126 * See {@see 'customize_save_response'}. 127 * 128 * When status is inserted, the placeholder post ID is stored in $previous_post_id. 129 * When status is error, the error is stored in $update_error. 130 * 131 * @since 4.3.0 132 * @var string updated|inserted|deleted|error 133 * 134 * @see WP_Customize_Nav_Menu_Item_Setting::update() 135 * @see WP_Customize_Nav_Menu_Item_Setting::amend_customize_save_response() 136 */ 137 public $update_status; 138 139 /** 140 * Any error object returned by wp_update_nav_menu_item() when setting is updated. 141 * 142 * @since 4.3.0 143 * @var WP_Error 144 * 145 * @see WP_Customize_Nav_Menu_Item_Setting::update() 146 * @see WP_Customize_Nav_Menu_Item_Setting::amend_customize_save_response() 147 */ 148 public $update_error; 149 150 /** 151 * Constructor. 152 * 153 * Any supplied $args override class property defaults. 154 * 155 * @since 4.3.0 156 * 157 * @throws Exception If $id is not valid for this setting type. 158 * 159 * @param WP_Customize_Manager $manager Customizer bootstrap instance. 160 * @param string $id A specific ID of the setting. 161 * Can be a theme mod or option name. 162 * @param array $args Optional. Setting arguments. 163 */ 164 public function __construct( WP_Customize_Manager $manager, $id, array $args = array() ) { 165 if ( empty( $manager->nav_menus ) ) { 166 throw new Exception( 'Expected WP_Customize_Manager::$nav_menus to be set.' ); 167 } 168 169 if ( ! preg_match( self::ID_PATTERN, $id, $matches ) ) { 170 throw new Exception( "Illegal widget setting ID: $id" ); 171 } 172 173 $this->post_id = (int) $matches['id']; 174 add_action( 'wp_update_nav_menu_item', array( $this, 'flush_cached_value' ), 10, 2 ); 175 176 parent::__construct( $manager, $id, $args ); 177 178 // Ensure that an initially-supplied value is valid. 179 if ( isset( $this->value ) ) { 180 $this->populate_value(); 181 foreach ( array_diff( array_keys( $this->default ), array_keys( $this->value ) ) as $missing ) { 182 throw new Exception( "Supplied nav_menu_item value missing property: $missing" ); 183 } 184 } 185 } 186 187 /** 188 * Clear the cached value when this nav menu item is updated. 189 * 190 * @since 4.3.0 191 * 192 * @param int $menu_id The term ID for the menu. 193 * @param int $menu_item_id The post ID for the menu item. 194 */ 195 public function flush_cached_value( $menu_id, $menu_item_id ) { 196 unset( $menu_id ); 197 if ( $menu_item_id === $this->post_id ) { 198 $this->value = null; 199 } 200 } 201 202 /** 203 * Get the instance data for a given nav_menu_item setting. 204 * 205 * @since 4.3.0 206 * 207 * @see wp_setup_nav_menu_item() 208 * 209 * @return array|false Instance data array, or false if the item is marked for deletion. 210 */ 211 public function value() { 212 $type_label = null; 213 if ( $this->is_previewed && get_current_blog_id() === $this->_previewed_blog_id ) { 214 $undefined = new stdClass(); // Symbol. 215 $post_value = $this->post_value( $undefined ); 216 217 if ( $undefined === $post_value ) { 218 $value = $this->_original_value; 219 } else { 220 $value = $post_value; 221 } 222 } elseif ( isset( $this->value ) ) { 223 $value = $this->value; 224 } else { 225 $value = false; 226 227 // Note that an ID of less than one indicates a nav_menu not yet inserted. 228 if ( $this->post_id > 0 ) { 229 $post = get_post( $this->post_id ); 230 if ( $post && self::POST_TYPE === $post->post_type ) { 231 $is_title_empty = empty( $post->post_title ); 232 $value = (array) wp_setup_nav_menu_item( $post ); 233 if ( isset( $value['type_label'] ) ) { 234 $type_label = $value['type_label']; 235 } 236 if ( $is_title_empty ) { 237 $value['title'] = ''; 238 } 239 } 240 } 241 242 if ( ! is_array( $value ) ) { 243 $value = $this->default; 244 } 245 246 // Cache the value for future calls to avoid having to re-call wp_setup_nav_menu_item(). 247 $this->value = $value; 248 $this->populate_value(); 249 $value = $this->value; 250 } 251 252 // These properties are read-only and are part of the setting for use in the Customizer UI. 253 if ( is_array( $value ) ) { 254 $value_obj = (object) $value; 255 $value['type_label'] = isset( $type_label ) ? $type_label : $this->get_type_label( $value_obj ); 256 $value['original_title'] = $this->get_original_title( $value_obj ); 257 } 258 259 return $value; 260 } 261 262 /** 263 * Prepares the value for editing on the client. 264 * 265 * @since 6.8.3 266 * 267 * @return array|false Value prepared for the client. 268 */ 269 public function js_value() { 270 $value = parent::js_value(); 271 if ( is_array( $value ) && isset( $value['original_title'] ) ) { 272 // Decode entities for the sake of displaying the original title as a placeholder. 273 $value['original_title'] = html_entity_decode( $value['original_title'], ENT_QUOTES, get_bloginfo( 'charset' ) ); 274 } 275 return $value; 276 } 277 278 /** 279 * Get original title. 280 * 281 * @since 4.7.0 282 * 283 * @param object $item Nav menu item. 284 * @return string The original title, without entity decoding. 285 */ 286 protected function get_original_title( $item ) { 287 $original_title = ''; 288 if ( 'post_type' === $item->type && ! empty( $item->object_id ) ) { 289 $original_object = get_post( $item->object_id ); 290 if ( $original_object ) { 291 /** This filter is documented in wp-includes/post-template.php */ 292 $original_title = apply_filters( 'the_title', $original_object->post_title, $original_object->ID ); 293 294 if ( '' === $original_title ) { 295 /* translators: %d: ID of a post. */ 296 $original_title = sprintf( __( '#%d (no title)' ), $original_object->ID ); 297 } 298 } 299 } elseif ( 'taxonomy' === $item->type && ! empty( $item->object_id ) ) { 300 $original_term_title = get_term_field( 'name', $item->object_id, $item->object, 'raw' ); 301 if ( ! is_wp_error( $original_term_title ) ) { 302 $original_title = $original_term_title; 303 } 304 } elseif ( 'post_type_archive' === $item->type ) { 305 $original_object = get_post_type_object( $item->object ); 306 if ( $original_object ) { 307 $original_title = $original_object->labels->archives; 308 } 309 } 310 return $original_title; 311 } 312 313 /** 314 * Get type label. 315 * 316 * @since 4.7.0 317 * 318 * @param object $item Nav menu item. 319 * @return string The type label. 320 */ 321 protected function get_type_label( $item ) { 322 if ( 'post_type' === $item->type ) { 323 $object = get_post_type_object( $item->object ); 324 if ( $object ) { 325 $type_label = $object->labels->singular_name; 326 } else { 327 $type_label = $item->object; 328 } 329 } elseif ( 'taxonomy' === $item->type ) { 330 $object = get_taxonomy( $item->object ); 331 if ( $object ) { 332 $type_label = $object->labels->singular_name; 333 } else { 334 $type_label = $item->object; 335 } 336 } elseif ( 'post_type_archive' === $item->type ) { 337 $type_label = __( 'Post Type Archive' ); 338 } else { 339 $type_label = __( 'Custom Link' ); 340 } 341 return $type_label; 342 } 343 344 /** 345 * Ensure that the value is fully populated with the necessary properties. 346 * 347 * Translates some properties added by wp_setup_nav_menu_item() and removes others. 348 * 349 * @since 4.3.0 350 * 351 * @see WP_Customize_Nav_Menu_Item_Setting::value() 352 */ 353 protected function populate_value() { 354 if ( ! is_array( $this->value ) ) { 355 return; 356 } 357 358 if ( isset( $this->value['menu_order'] ) ) { 359 $this->value['position'] = $this->value['menu_order']; 360 unset( $this->value['menu_order'] ); 361 } 362 if ( isset( $this->value['post_status'] ) ) { 363 $this->value['status'] = $this->value['post_status']; 364 unset( $this->value['post_status'] ); 365 } 366 367 if ( ! isset( $this->value['nav_menu_term_id'] ) && $this->post_id > 0 ) { 368 $menus = wp_get_post_terms( 369 $this->post_id, 370 WP_Customize_Nav_Menu_Setting::TAXONOMY, 371 array( 372 'fields' => 'ids', 373 ) 374 ); 375 if ( ! empty( $menus ) ) { 376 $this->value['nav_menu_term_id'] = array_shift( $menus ); 377 } else { 378 $this->value['nav_menu_term_id'] = 0; 379 } 380 } 381 382 foreach ( array( 'object_id', 'menu_item_parent', 'nav_menu_term_id' ) as $key ) { 383 if ( ! is_int( $this->value[ $key ] ) ) { 384 $this->value[ $key ] = (int) $this->value[ $key ]; 385 } 386 } 387 foreach ( array( 'classes', 'xfn' ) as $key ) { 388 if ( is_array( $this->value[ $key ] ) ) { 389 $this->value[ $key ] = implode( ' ', $this->value[ $key ] ); 390 } 391 } 392 393 if ( ! isset( $this->value['title'] ) ) { 394 $this->value['title'] = ''; 395 } 396 397 if ( ! isset( $this->value['_invalid'] ) ) { 398 $this->value['_invalid'] = false; 399 $is_known_invalid = ( 400 ( ( 'post_type' === $this->value['type'] || 'post_type_archive' === $this->value['type'] ) && ! post_type_exists( $this->value['object'] ) ) 401 || 402 ( 'taxonomy' === $this->value['type'] && ! taxonomy_exists( $this->value['object'] ) ) 403 ); 404 if ( $is_known_invalid ) { 405 $this->value['_invalid'] = true; 406 } 407 } 408 409 // Remove remaining properties available on a setup nav_menu_item post object which aren't relevant to the setting value. 410 $irrelevant_properties = array( 411 'ID', 412 'comment_count', 413 'comment_status', 414 'db_id', 415 'filter', 416 'guid', 417 'ping_status', 418 'pinged', 419 'post_author', 420 'post_content', 421 'post_content_filtered', 422 'post_date', 423 'post_date_gmt', 424 'post_excerpt', 425 'post_mime_type', 426 'post_modified', 427 'post_modified_gmt', 428 'post_name', 429 'post_parent', 430 'post_password', 431 'post_title', 432 'post_type', 433 'to_ping', 434 ); 435 foreach ( $irrelevant_properties as $property ) { 436 unset( $this->value[ $property ] ); 437 } 438 } 439 440 /** 441 * Handle previewing the setting. 442 * 443 * @since 4.3.0 444 * @since 4.4.0 Added boolean return value. 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(); 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 * 484 * @see wp_get_nav_menu_items() 485 * 486 * @param WP_Post[] $items An array of menu item post objects. 487 * @param WP_Term $menu The menu object. 488 * @param array $args An array of arguments used to retrieve menu item objects. 489 * @return WP_Post[] Array of menu item objects. 490 */ 491 public function filter_wp_get_nav_menu_items( $items, $menu, $args ) { 492 $this_item = $this->value(); 493 $current_nav_menu_term_id = null; 494 if ( isset( $this_item['nav_menu_term_id'] ) ) { 495 $current_nav_menu_term_id = $this_item['nav_menu_term_id']; 496 unset( $this_item['nav_menu_term_id'] ); 497 } 498 499 $should_filter = ( 500 $menu->term_id === $this->original_nav_menu_term_id 501 || 502 $menu->term_id === $current_nav_menu_term_id 503 ); 504 if ( ! $should_filter ) { 505 return $items; 506 } 507 508 // Handle deleted menu item, or menu item moved to another menu. 509 $should_remove = ( 510 false === $this_item 511 || 512 ( isset( $this_item['_invalid'] ) && true === $this_item['_invalid'] ) 513 || 514 ( 515 $this->original_nav_menu_term_id === $menu->term_id 516 && 517 $current_nav_menu_term_id !== $this->original_nav_menu_term_id 518 ) 519 ); 520 if ( $should_remove ) { 521 $filtered_items = array(); 522 foreach ( $items as $item ) { 523 if ( $item->db_id !== $this->post_id ) { 524 $filtered_items[] = $item; 525 } 526 } 527 return $filtered_items; 528 } 529 530 $mutated = false; 531 $should_update = ( 532 is_array( $this_item ) 533 && 534 $current_nav_menu_term_id === $menu->term_id 535 ); 536 if ( $should_update ) { 537 foreach ( $items as $item ) { 538 if ( $item->db_id === $this->post_id ) { 539 foreach ( get_object_vars( $this->value_as_wp_post_nav_menu_item() ) as $key => $value ) { 540 $item->$key = $value; 541 } 542 $mutated = true; 543 } 544 } 545 546 // Not found so we have to append it.. 547 if ( ! $mutated ) { 548 $items[] = $this->value_as_wp_post_nav_menu_item(); 549 } 550 } 551 552 return $items; 553 } 554 555 /** 556 * Re-apply the tail logic also applied on $items by wp_get_nav_menu_items(). 557 * 558 * @since 4.3.0 559 * 560 * @see wp_get_nav_menu_items() 561 * 562 * @param WP_Post[] $items An array of menu item post objects. 563 * @param WP_Term $menu The menu object. 564 * @param array $args An array of arguments used to retrieve menu item objects. 565 * @return WP_Post[] Array of menu item objects. 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 if ( ARRAY_A === $args['output'] ) { 577 $items = wp_list_sort( 578 $items, 579 array( 580 $args['output_key'] => 'ASC', 581 ) 582 ); 583 $i = 1; 584 585 foreach ( $items as $k => $item ) { 586 $items[ $k ]->{$args['output_key']} = $i++; 587 } 588 } 589 590 return $items; 591 } 592 593 /** 594 * Get the value emulated into a WP_Post and set up as a nav_menu_item. 595 * 596 * @since 4.3.0 597 * 598 * @return WP_Post With wp_setup_nav_menu_item() applied. 599 */ 600 public function value_as_wp_post_nav_menu_item() { 601 $item = (object) $this->value(); 602 unset( $item->nav_menu_term_id ); 603 604 $item->post_status = $item->status; 605 unset( $item->status ); 606 607 $item->post_type = 'nav_menu_item'; 608 $item->menu_order = $item->position; 609 unset( $item->position ); 610 611 if ( empty( $item->title ) && ! empty( $item->original_title ) ) { 612 $item->title = $item->original_title; // This is NOT entity-decoded. It comes from self::get_original_title(). 613 } 614 if ( $item->title ) { 615 $item->post_title = $item->title; 616 } 617 618 // 'classes' should be an array, as in wp_setup_nav_menu_item(). 619 if ( isset( $item->classes ) && is_scalar( $item->classes ) ) { 620 $item->classes = explode( ' ', $item->classes ); 621 } 622 623 $item->ID = $this->post_id; 624 $item->db_id = $this->post_id; 625 $post = new WP_Post( (object) $item ); 626 627 if ( empty( $post->post_author ) ) { 628 $post->post_author = get_current_user_id(); 629 } 630 631 if ( ! isset( $post->type_label ) ) { 632 $post->type_label = $this->get_type_label( $post ); 633 } 634 635 // Ensure nav menu item URL is set according to linked object. 636 if ( 'post_type' === $post->type && ! empty( $post->object_id ) ) { 637 $post->url = get_permalink( $post->object_id ); 638 } elseif ( 'taxonomy' === $post->type && ! empty( $post->object ) && ! empty( $post->object_id ) ) { 639 $post->url = get_term_link( (int) $post->object_id, $post->object ); 640 } elseif ( 'post_type_archive' === $post->type && ! empty( $post->object ) ) { 641 $post->url = get_post_type_archive_link( $post->object ); 642 } 643 if ( is_wp_error( $post->url ) ) { 644 $post->url = ''; 645 } 646 647 /** This filter is documented in wp-includes/nav-menu.php */ 648 $post->attr_title = apply_filters( 'nav_menu_attr_title', $post->attr_title ); 649 650 /** This filter is documented in wp-includes/nav-menu.php */ 651 $post->description = apply_filters( 'nav_menu_description', wp_trim_words( $post->description, 200 ) ); 652 653 /** This filter is documented in wp-includes/nav-menu.php */ 654 $post = apply_filters( 'wp_setup_nav_menu_item', $post ); 655 656 return $post; 657 } 658 659 /** 660 * Sanitize an input. 661 * 662 * Note that parent::sanitize() erroneously does wp_unslash() on $value, but 663 * we remove that in this override. 664 * 665 * @since 4.3.0 666 * @since 5.9.0 Renamed `$menu_item_value` to `$value` for PHP 8 named parameter support. 667 * 668 * @param array|false $value The menu item value to sanitize. 669 * @return array|false|null|WP_Error Null or WP_Error if an input isn't valid. False if it is marked for deletion. 670 * Otherwise the sanitized value. 671 */ 672 public function sanitize( $value ) { 673 // Restores the more descriptive, specific name for use within this method. 674 $menu_item_value = $value; 675 676 // Menu is marked for deletion. 677 if ( false === $menu_item_value ) { 678 return $menu_item_value; 679 } 680 681 // Invalid. 682 if ( ! is_array( $menu_item_value ) ) { 683 return null; 684 } 685 686 $default = array( 687 'object_id' => 0, 688 'object' => '', 689 'menu_item_parent' => 0, 690 'position' => 0, 691 'type' => 'custom', 692 'title' => '', 693 'url' => '', 694 'target' => '', 695 'attr_title' => '', 696 'description' => '', 697 'classes' => '', 698 'xfn' => '', 699 'status' => 'publish', 700 'original_title' => '', 701 'nav_menu_term_id' => 0, 702 '_invalid' => false, 703 ); 704 $menu_item_value = array_merge( $default, $menu_item_value ); 705 $menu_item_value = wp_array_slice_assoc( $menu_item_value, array_keys( $default ) ); 706 $menu_item_value['position'] = (int) $menu_item_value['position']; 707 708 foreach ( array( 'object_id', 'menu_item_parent', 'nav_menu_term_id' ) as $key ) { 709 // Note we need to allow negative-integer IDs for previewed objects not inserted yet. 710 $menu_item_value[ $key ] = (int) $menu_item_value[ $key ]; 711 } 712 713 foreach ( array( 'type', 'object', 'target' ) as $key ) { 714 $menu_item_value[ $key ] = sanitize_key( $menu_item_value[ $key ] ); 715 } 716 717 foreach ( array( 'xfn', 'classes' ) as $key ) { 718 $value = $menu_item_value[ $key ]; 719 if ( ! is_array( $value ) ) { 720 $value = explode( ' ', $value ); 721 } 722 $menu_item_value[ $key ] = implode( ' ', array_map( 'sanitize_html_class', $value ) ); 723 } 724 725 // Apply the same filters as when calling wp_insert_post(). 726 727 /** This filter is documented in wp-includes/post.php */ 728 $menu_item_value['title'] = wp_unslash( apply_filters( 'title_save_pre', wp_slash( $menu_item_value['title'] ) ) ); 729 730 /** This filter is documented in wp-includes/post.php */ 731 $menu_item_value['attr_title'] = wp_unslash( apply_filters( 'excerpt_save_pre', wp_slash( $menu_item_value['attr_title'] ) ) ); 732 733 /** This filter is documented in wp-includes/post.php */ 734 $menu_item_value['description'] = wp_unslash( apply_filters( 'content_save_pre', wp_slash( $menu_item_value['description'] ) ) ); 735 736 if ( '' !== $menu_item_value['url'] ) { 737 $menu_item_value['url'] = sanitize_url( $menu_item_value['url'] ); 738 if ( '' === $menu_item_value['url'] ) { 739 return new WP_Error( 'invalid_url', __( 'Invalid URL.' ) ); // Fail sanitization if URL is invalid. 740 } 741 } 742 if ( 'publish' !== $menu_item_value['status'] ) { 743 $menu_item_value['status'] = 'draft'; 744 } 745 746 $menu_item_value['_invalid'] = (bool) $menu_item_value['_invalid']; 747 748 /** This filter is documented in wp-includes/class-wp-customize-setting.php */ 749 return apply_filters( "customize_sanitize_{$this->id}", $menu_item_value, $this ); 750 } 751 752 /** 753 * Creates/updates the nav_menu_item post for this setting. 754 * 755 * Any created menu items will have their assigned post IDs exported to the client 756 * via the {@see 'customize_save_response'} filter. Likewise, any errors will be 757 * exported to the client via the customize_save_response() filter. 758 * 759 * To delete a menu, the client can send false as the value. 760 * 761 * @since 4.3.0 762 * 763 * @see wp_update_nav_menu_item() 764 * 765 * @param array|false $value The menu item array to update. If false, then the menu item will be deleted 766 * entirely. See WP_Customize_Nav_Menu_Item_Setting::$default for what the value 767 * should consist of. 768 * @return null|void 769 */ 770 protected function update( $value ) { 771 if ( $this->is_updated ) { 772 return; 773 } 774 775 $this->is_updated = true; 776 $is_placeholder = ( $this->post_id < 0 ); 777 $is_delete = ( false === $value ); 778 779 // Update the cached value. 780 $this->value = $value; 781 782 add_filter( 'customize_save_response', array( $this, 'amend_customize_save_response' ) ); 783 784 if ( $is_delete ) { 785 // If the current setting post is a placeholder, a delete request is a no-op. 786 if ( $is_placeholder ) { 787 $this->update_status = 'deleted'; 788 } else { 789 $r = wp_delete_post( $this->post_id, true ); 790 791 if ( false === $r ) { 792 $this->update_error = new WP_Error( 'delete_failure' ); 793 $this->update_status = 'error'; 794 } else { 795 $this->update_status = 'deleted'; 796 } 797 // @todo send back the IDs for all associated nav menu items deleted, so these settings (and controls) can be removed from Customizer? 798 } 799 } else { 800 801 // Handle saving menu items for menus that are being newly-created. 802 if ( $value['nav_menu_term_id'] < 0 ) { 803 $nav_menu_setting_id = sprintf( 'nav_menu[%s]', $value['nav_menu_term_id'] ); 804 $nav_menu_setting = $this->manager->get_setting( $nav_menu_setting_id ); 805 806 if ( ! $nav_menu_setting || ! ( $nav_menu_setting instanceof WP_Customize_Nav_Menu_Setting ) ) { 807 $this->update_status = 'error'; 808 $this->update_error = new WP_Error( 'unexpected_nav_menu_setting' ); 809 return; 810 } 811 812 if ( false === $nav_menu_setting->save() ) { 813 $this->update_status = 'error'; 814 $this->update_error = new WP_Error( 'nav_menu_setting_failure' ); 815 return; 816 } 817 818 if ( (int) $value['nav_menu_term_id'] !== $nav_menu_setting->previous_term_id ) { 819 $this->update_status = 'error'; 820 $this->update_error = new WP_Error( 'unexpected_previous_term_id' ); 821 return; 822 } 823 824 $value['nav_menu_term_id'] = $nav_menu_setting->term_id; 825 } 826 827 // Handle saving a nav menu item that is a child of a nav menu item being newly-created. 828 if ( $value['menu_item_parent'] < 0 ) { 829 $parent_nav_menu_item_setting_id = sprintf( 'nav_menu_item[%s]', $value['menu_item_parent'] ); 830 $parent_nav_menu_item_setting = $this->manager->get_setting( $parent_nav_menu_item_setting_id ); 831 832 if ( ! $parent_nav_menu_item_setting || ! ( $parent_nav_menu_item_setting instanceof WP_Customize_Nav_Menu_Item_Setting ) ) { 833 $this->update_status = 'error'; 834 $this->update_error = new WP_Error( 'unexpected_nav_menu_item_setting' ); 835 return; 836 } 837 838 if ( false === $parent_nav_menu_item_setting->save() ) { 839 $this->update_status = 'error'; 840 $this->update_error = new WP_Error( 'nav_menu_item_setting_failure' ); 841 return; 842 } 843 844 if ( (int) $value['menu_item_parent'] !== $parent_nav_menu_item_setting->previous_post_id ) { 845 $this->update_status = 'error'; 846 $this->update_error = new WP_Error( 'unexpected_previous_post_id' ); 847 return; 848 } 849 850 $value['menu_item_parent'] = $parent_nav_menu_item_setting->post_id; 851 } 852 853 // Insert or update menu. 854 $menu_item_data = array( 855 'menu-item-object-id' => $value['object_id'], 856 'menu-item-object' => $value['object'], 857 'menu-item-parent-id' => $value['menu_item_parent'], 858 'menu-item-position' => $value['position'], 859 'menu-item-type' => $value['type'], 860 'menu-item-title' => $value['title'], 861 'menu-item-url' => $value['url'], 862 'menu-item-description' => $value['description'], 863 'menu-item-attr-title' => $value['attr_title'], 864 'menu-item-target' => $value['target'], 865 'menu-item-classes' => $value['classes'], 866 'menu-item-xfn' => $value['xfn'], 867 'menu-item-status' => $value['status'], 868 ); 869 870 $r = wp_update_nav_menu_item( 871 $value['nav_menu_term_id'], 872 $is_placeholder ? 0 : $this->post_id, 873 wp_slash( $menu_item_data ) 874 ); 875 876 if ( is_wp_error( $r ) ) { 877 $this->update_status = 'error'; 878 $this->update_error = $r; 879 } else { 880 if ( $is_placeholder ) { 881 $this->previous_post_id = $this->post_id; 882 $this->post_id = $r; 883 $this->update_status = 'inserted'; 884 } else { 885 $this->update_status = 'updated'; 886 } 887 } 888 } 889 } 890 891 /** 892 * Export data for the JS client. 893 * 894 * @since 4.3.0 895 * 896 * @see WP_Customize_Nav_Menu_Item_Setting::update() 897 * 898 * @param array $data Additional information passed back to the 'saved' event on `wp.customize`. 899 * @return array Save response data. 900 */ 901 public function amend_customize_save_response( $data ) { 902 if ( ! isset( $data['nav_menu_item_updates'] ) ) { 903 $data['nav_menu_item_updates'] = array(); 904 } 905 906 $data['nav_menu_item_updates'][] = array( 907 'post_id' => $this->post_id, 908 'previous_post_id' => $this->previous_post_id, 909 'error' => $this->update_error ? $this->update_error->get_error_code() : null, 910 'status' => $this->update_status, 911 ); 912 return $data; 913 } 914 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated : Wed Oct 15 08:20:04 2025 | Cross-referenced by PHPXref |