[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

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       * 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  }


Generated : Tue Apr 23 08:20:01 2024 Cross-referenced by PHPXref