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