[ Index ]

PHP Cross Reference of WordPress

title

Body

[close]

/wp-includes/customize/ -> class-wp-customize-nav-menu-item-setting.php (source)

   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  }


Generated: Sat Apr 27 01:00:02 2024 Cross-referenced by PHPXref 0.7.1