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